From 54cc7e18906cd72f34b45020c09a5d4842ed12e3 Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Mon, 7 Oct 2019 21:24:18 +0200 Subject: [PATCH 01/21] - Placed VNA hardware specific code into a separate module --- NanoVNASaver/Hardware.py | 142 ++++++++++++++++++++++++++++++++++- NanoVNASaver/NanoVNASaver.py | 97 +++--------------------- NanoVNASaver/SweepWorker.py | 13 ++-- 3 files changed, 160 insertions(+), 92 deletions(-) diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index 6b06f947..932bda7b 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -13,15 +13,153 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import collections +import logging +from time import sleep +from typing import List + +import serial + +Datapoint = collections.namedtuple('Datapoint', 'freq re im') + +logger = logging.getLogger(__name__) class VNA: - pass + def __init__(self, app, serialPort: serial.Serial): + from NanoVNASaver.NanoVNASaver import NanoVNASaver + self.app: NanoVNASaver = app + self.serial = serialPort + + @staticmethod + def getVNA(app, serialPort: serial.Serial) -> 'VNA': + logger.info("Finding correct VNA type") + tmp_vna = VNA(app, serialPort) + tmp_vna.flushSerialBuffers() + firmware = tmp_vna.readFirmware() + if firmware.find("NanoVNA") > 0: + return NanoVNA(app, serialPort) + return InvalidVNA(app, serialPort) + + def readFrequencies(self) -> List[str]: + pass + + def readValues11(self) -> List[Datapoint]: + pass + + def readValues21(self) -> List[Datapoint]: + pass + + def flushSerialBuffers(self): + if self.app.serialLock.acquire(): + self.serial.write(b"\r\n\r\n") + sleep(0.1) + self.serial.reset_input_buffer() + self.serial.reset_output_buffer() + sleep(0.1) + self.app.serialLock.release() + + def readFirmware(self): + if self.app.serialLock.acquire(): + result = "" + try: + data = "a" + while data != "": + data = self.serial.readline().decode('ascii') + self.serial.write("info\r".encode('ascii')) + result = "" + data = "" + sleep(0.01) + while "ch>" not in data: + data = self.serial.readline().decode('ascii') + result += data + except serial.SerialException as exc: + logger.exception("Exception while reading firmware data: %s", exc) + finally: + self.app.serialLock.release() + return result + else: + logger.error("Unable to acquire serial lock to read firmware.") + return "" + + def readValues(self, value): + if self.app.serialLock.acquire(): + try: + data = "a" + while data != "": + data = self.serial.readline().decode('ascii') + + # Then send the command to read data + self.serial.write(str(value + "\r").encode('ascii')) + result = "" + data = "" + sleep(0.05) + while "ch>" not in data: + data = self.serial.readline().decode('ascii') + result += data + values = result.split("\r\n") + except serial.SerialException as exc: + logger.exception("Exception while reading %s: %s", value, exc) + return + finally: + self.app.serialLock.release() + return values[1:102] + else: + logger.error("Unable to acquire serial lock to read %s", value) + return + + def writeSerial(self, command): + if not self.serial.is_open: + logger.warning("Writing without serial port being opened (%s)", command) + return + if self.app.serialLock.acquire(): + try: + self.serial.write(str(command + "\r").encode('ascii')) + self.serial.readline() + except serial.SerialException as exc: + logger.exception("Exception while writing to serial port (%s): %s", command, exc) + self.app.serialLock.release() + return + + def setSweep(self, start, stop): + self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101") + + +class InvalidVNA(VNA): + def __init__(self): + pass + + def setSweep(self, start, stop): + return + + def writeSerial(self, command): + return + + def readFirmware(self): + return + + def readFrequencies(self) -> List[int]: + return [] + + def readValues11(self) -> List[Datapoint]: + return [] + + def readValues21(self) -> List[Datapoint]: + return [] + + def readValues(self, value): + return + + def flushSerialBuffers(self): + return class NanoVNA(VNA): - pass + def __init__(self, app, serialPort): + super().__init__(app, serialPort) + def readFrequencies(self) -> List[str]: + return self.readValues("frequencies") class NanoVNA_F(NanoVNA): pass diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index cf8829c7..a5effba7 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -28,6 +28,7 @@ from PyQt5.QtCore import QModelIndex from serial.tools import list_ports +from NanoVNASaver.Hardware import VNA, InvalidVNA from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagChart, QualityFactorChart, TDRChart, \ RealImaginaryChart from .Calibration import CalibrationWindow, Calibration @@ -62,7 +63,8 @@ def __init__(self): "NanoVNASaver", "NanoVNASaver") print("Settings: " + self.settings.fileName()) self.threadpool = QtCore.QThreadPool() - self.worker = SweepWorker(self) + self.vna = InvalidVNA() + self.worker = SweepWorker(self, self.vna) self.bands = BandsModel() @@ -488,10 +490,6 @@ def __init__(self): # Right side ################################################################################################################ - self.worker.signals.updated.connect(self.dataUpdated) - self.worker.signals.finished.connect(self.sweepFinished) - self.worker.signals.sweepError.connect(self.showSweepError) - logger.debug("Finished building interface") def rescanSerialPort(self): @@ -577,15 +575,6 @@ def serialButtonClick(self): self.startSerial() return - def flushSerialBuffers(self): - if self.serialLock.acquire(): - self.serial.write(b"\r\n\r\n") - sleep(0.1) - self.serial.reset_input_buffer() - self.serial.reset_output_buffer() - sleep(0.1) - self.serialLock.release() - def startSerial(self): if self.serialLock.acquire(): self.serialPort = self.serialPortInput.text() @@ -602,12 +591,16 @@ def startSerial(self): self.serialLock.release() sleep(0.05) - self.flushSerialBuffers() - sleep(0.05) + self.vna = VNA.getVNA(self, self.serial) + self.worker = SweepWorker(self, self.vna) + + self.worker.signals.updated.connect(self.dataUpdated) + self.worker.signals.finished.connect(self.sweepFinished) + self.worker.signals.sweepError.connect(self.showSweepError) - logger.info(self.readFirmware()) + logger.info(self.vna.readFirmware()) - frequencies = self.readValues("frequencies") + frequencies = self.vna.readFrequencies() if frequencies: logger.info("Read starting frequency %s and end frequency %s", frequencies[0], frequencies[100]) if int(frequencies[0]) == int(frequencies[100]) and (self.sweepStartInput.text() == "" or self.sweepEndInput.text() == ""): @@ -620,7 +613,6 @@ def startSerial(self): else: logger.warning("No frequencies read") return - logger.debug("Starting initial sweep") self.sweep() return @@ -632,22 +624,6 @@ def stopSerial(self): self.serialLock.release() self.btnSerialToggle.setText("Connect to NanoVNA") - def writeSerial(self, command): - if not self.serial.is_open: - logger.warning("Writing without serial port being opened (%s)", command) - return - if self.serialLock.acquire(): - try: - self.serial.write(str(command + "\r").encode('ascii')) - self.serial.readline() - except serial.SerialException as exc: - logger.exception("Exception while writing to serial port (%s): %s", command, exc) - self.serialLock.release() - return - - def setSweep(self, start, stop): - self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101") - def toggleSweepSettings(self, disabled): self.sweepStartInput.setDisabled(disabled) self.sweepEndInput.setDisabled(disabled) @@ -676,61 +652,12 @@ def sweep(self): if self.sweepCountInput.text().isdigit(): self.settings.setValue("Segments", self.sweepCountInput.text()) + logger.debug("Starting worker thread") self.threadpool.start(self.worker) def stopSweep(self): self.worker.stopped = True - def readFirmware(self): - if self.serialLock.acquire(): - result = "" - try: - data = "a" - while data != "": - data = self.serial.readline().decode('ascii') - # Then send the command to read data - self.serial.write("info\r".encode('ascii')) - result = "" - data = "" - sleep(0.01) - while "ch>" not in data: - data = self.serial.readline().decode('ascii') - result += data - except serial.SerialException as exc: - logger.exception("Exception while reading firmware data: %s", exc) - self.serialLock.release() - return result - else: - logger.error("Unable to acquire serial lock to read firmware.") - return "" - - def readValues(self, value): - if self.serialLock.acquire(): - try: - data = "a" - while data != "": - data = self.serial.readline().decode('ascii') - - # Then send the command to read data - self.serial.write(str(value + "\r").encode('ascii')) - result = "" - data = "" - sleep(0.05) - while "ch>" not in data: - data = self.serial.readline().decode('ascii') - result += data - values = result.split("\r\n") - except serial.SerialException as exc: - logger.exception("Exception while reading %s: %s", value, exc) - self.serialLock.release() - return - - self.serialLock.release() - return values[1:102] - else: - logger.error("Unable to acquire serial lock to read %s", value) - return - def saveData(self, data, data12, source=None): if self.dataLock.acquire(blocking=True): self.data = data diff --git a/NanoVNASaver/SweepWorker.py b/NanoVNASaver/SweepWorker.py index a3d1dc80..df31cd2d 100644 --- a/NanoVNASaver/SweepWorker.py +++ b/NanoVNASaver/SweepWorker.py @@ -24,6 +24,8 @@ import NanoVNASaver import logging +from NanoVNASaver.Hardware import VNA + logger = logging.getLogger(__name__) Datapoint = collections.namedtuple('Datapoint', 'freq re im') @@ -36,11 +38,12 @@ class WorkerSignals(QtCore.QObject): class SweepWorker(QtCore.QRunnable): - def __init__(self, app: NanoVNASaver): + def __init__(self, app: NanoVNASaver, vna: VNA): super().__init__() logger.info("Initializing SweepWorker") self.signals = WorkerSignals() self.app = app + self.vna = vna self.noSweeps = 1 self.setAutoDelete(False) self.percentage = 0 @@ -156,7 +159,7 @@ def run(self): logger.debug("Resetting NanoVNA sweep to full range: %d to %d", NanoVNASaver.parseFrequency(self.app.sweepStartInput.text()), NanoVNASaver.parseFrequency(self.app.sweepEndInput.text())) - self.app.setSweep(NanoVNASaver.parseFrequency(self.app.sweepStartInput.text()), NanoVNASaver.parseFrequency(self.app.sweepEndInput.text())) + self.vna.setSweep(NanoVNASaver.parseFrequency(self.app.sweepStartInput.text()), NanoVNASaver.parseFrequency(self.app.sweepEndInput.text())) self.percentage = 100 logger.debug("Sending \"finished\" signal") @@ -270,7 +273,7 @@ def truncate(self, values: List[List[tuple]], count): def readSegment(self, start, stop): logger.debug("Setting sweep range to %d to %d", start, stop) - self.app.setSweep(start, stop) + self.vna.setSweep(start, stop) sleep(1) # TODO This long delay seems to fix the weird data transitions we were seeing by getting partial # sweeps. Clearly something needs to be done, maybe at firmware level, to address this fully. @@ -296,7 +299,7 @@ def readData(self, data): while not done: done = True returndata = [] - tmpdata = self.app.readValues(data) + tmpdata = self.vna.readValues(data) if not tmpdata: logger.warning("Read no values") raise NanoVNAValueException("Failed reading data: Returned no values.") @@ -338,7 +341,7 @@ def readFreq(self): while not done: done = True returnfreq = [] - tmpfreq = self.app.readValues("frequencies") + tmpfreq = self.vna.readValues("frequencies") if not tmpfreq: logger.warning("Read no frequencies") raise NanoVNAValueException("Failed reading frequencies: Returned no values.") From c245cbac871353cb5a6b127e9bde7ef2d64cbb54 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 8 Oct 2019 11:43:13 +0200 Subject: [PATCH 02/21] - Making a version parsing class, first steps --- NanoVNASaver/Hardware.py | 43 ++++++++++++++++++++++++++++++++-------- NanoVNASaver/about.py | 2 +- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index 932bda7b..bcbdfe0f 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -15,6 +15,7 @@ # along with this program. If not, see . import collections import logging +import re from time import sleep from typing import List @@ -44,10 +45,13 @@ def getVNA(app, serialPort: serial.Serial) -> 'VNA': def readFrequencies(self) -> List[str]: pass - def readValues11(self) -> List[Datapoint]: + def readValues11(self) -> List[str]: pass - def readValues21(self) -> List[Datapoint]: + def readValues21(self) -> List[str]: + pass + + def resetSweep(self, start: int, stop: int): pass def flushSerialBuffers(self): @@ -59,7 +63,7 @@ def flushSerialBuffers(self): sleep(0.1) self.app.serialLock.release() - def readFirmware(self): + def readFirmware(self) -> str: if self.app.serialLock.acquire(): result = "" try: @@ -82,7 +86,7 @@ def readFirmware(self): logger.error("Unable to acquire serial lock to read firmware.") return "" - def readValues(self, value): + def readValues(self, value) -> List[str]: if self.app.serialLock.acquire(): try: data = "a" @@ -100,13 +104,13 @@ def readValues(self, value): values = result.split("\r\n") except serial.SerialException as exc: logger.exception("Exception while reading %s: %s", value, exc) - return + return [] finally: self.app.serialLock.release() return values[1:102] else: logger.error("Unable to acquire serial lock to read %s", value) - return + return [] def writeSerial(self, command): if not self.serial.is_open: @@ -132,6 +136,9 @@ def __init__(self): def setSweep(self, start, stop): return + def resetSweep(self, start, stop): + return + def writeSerial(self, command): return @@ -141,10 +148,10 @@ def readFirmware(self): def readFrequencies(self) -> List[int]: return [] - def readValues11(self) -> List[Datapoint]: + def readValues11(self) -> List[str]: return [] - def readValues21(self) -> List[Datapoint]: + def readValues21(self) -> List[str]: return [] def readValues(self, value): @@ -161,5 +168,25 @@ def __init__(self, app, serialPort): def readFrequencies(self) -> List[str]: return self.readValues("frequencies") + def readValues11(self) -> List[str]: + return self.readValues("data 1") + + def readValues21(self) -> List[str]: + return self.readValues("data 1") + + def resetSweep(self, start: int, stop: int): + self.setSweep(start, stop) + + class NanoVNA_F(NanoVNA): pass + + +class Version: + def __init__(self, version_string): + self.version_string = version_string + results = re.match(r"(version )?(\d+)\.(\d+)\.(\d+\w+)", version_string) + if results: + self.major = results.group(1) + self.minor = results.group(2) + self.revision = results.group(3) diff --git a/NanoVNASaver/about.py b/NanoVNASaver/about.py index 6ce410a5..54f39be7 100644 --- a/NanoVNASaver/about.py +++ b/NanoVNASaver/about.py @@ -14,5 +14,5 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -version = '0.1.1alpha' +version = '0.1.2alpha' debug = True From 6e4d3240e8c92d040a9bd0b22204845551137cc4 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Tue, 8 Oct 2019 13:49:38 +0200 Subject: [PATCH 03/21] - Corrected roll-off calculation --- NanoVNASaver/Analysis.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NanoVNASaver/Analysis.py b/NanoVNASaver/Analysis.py index b0e4e30e..7668c551 100644 --- a/NanoVNASaver/Analysis.py +++ b/NanoVNASaver/Analysis.py @@ -577,10 +577,11 @@ def runAnalysis(self): self.upper_six_db_label.setText(NanoVNASaver.formatFrequency(upper_six_db_cutoff_frequency)) upper_six_db_attenuation = NanoVNASaver.gain(self.app.data21[upper_six_db_location]) - upper_max_attenuation = NanoVNASaver.gain(self.app.data21[0]) - frequency_factor = self.app.data21[0].freq / upper_six_db_cutoff_frequency + upper_max_attenuation = NanoVNASaver.gain(self.app.data21[len(self.app.data21)-1]) + frequency_factor = upper_six_db_cutoff_frequency / self.app.data21[len(self.app.data21)-1].freq upper_attenuation = (upper_max_attenuation - upper_six_db_attenuation) - logger.debug("Measured points: %d Hz and %d Hz", upper_six_db_cutoff_frequency, self.app.data21[0].freq) + logger.debug("Measured points: %d Hz and %d Hz", upper_six_db_cutoff_frequency, + self.app.data21[len(self.app.data21)-1].freq) logger.debug("%d dB over %f factor", upper_attenuation, frequency_factor) octave_attenuation = upper_attenuation / (math.log10(frequency_factor) / math.log10(2)) self.upper_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") From 5793b3a85f9123d3b6f6fb6ed3952d5ef520bf6a Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Tue, 8 Oct 2019 21:30:45 +0200 Subject: [PATCH 04/21] - Reading version info, and switching to scan function if possible --- NanoVNASaver/Hardware.py | 72 ++++++++++++++++++++++++++++++++----- NanoVNASaver/SweepWorker.py | 7 ++-- 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index bcbdfe0f..c21c7fb8 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -74,7 +74,7 @@ def readFirmware(self) -> str: result = "" data = "" sleep(0.01) - while "ch>" not in data: + while data != "ch> ": data = self.serial.readline().decode('ascii') result += data except serial.SerialException as exc: @@ -87,6 +87,7 @@ def readFirmware(self) -> str: return "" def readValues(self, value) -> List[str]: + logger.debug("VNA reading %s", value) if self.app.serialLock.acquire(): try: data = "a" @@ -98,7 +99,7 @@ def readValues(self, value) -> List[str]: result = "" data = "" sleep(0.05) - while "ch>" not in data: + while data != "ch> ": data = self.serial.readline().decode('ascii') result += data values = result.split("\r\n") @@ -107,7 +108,7 @@ def readValues(self, value) -> List[str]: return [] finally: self.app.serialLock.release() - return values[1:102] + return values[1:-1] else: logger.error("Unable to acquire serial lock to read %s", value) return [] @@ -122,7 +123,8 @@ def writeSerial(self, command): self.serial.readline() except serial.SerialException as exc: logger.exception("Exception while writing to serial port (%s): %s", command, exc) - self.app.serialLock.release() + finally: + self.app.serialLock.release() return def setSweep(self, start, stop): @@ -164,6 +166,15 @@ def flushSerialBuffers(self): class NanoVNA(VNA): def __init__(self, app, serialPort): super().__init__(app, serialPort) + self.version = Version(self.readVersion()) + + logger.debug("Testing against 0.2.0") + if self.version > Version("0.2.0"): + logger.debug("Newer than 0.2.0, using new scan command.") + self.useScan = True + else: + logger.debug("Older than 0.2.0, using old sweep command.") + self.useScan = False def readFrequencies(self) -> List[str]: return self.readValues("frequencies") @@ -175,7 +186,40 @@ def readValues21(self) -> List[str]: return self.readValues("data 1") def resetSweep(self, start: int, stop: int): - self.setSweep(start, stop) + self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101") + 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): + if self.useScan: + self.writeSerial("scan " + str(start) + " " + str(stop) + " 101") + else: + self.writeSerial("sweep " + str(start) + " " + str(stop) + " 101") + sleep(1) class NanoVNA_F(NanoVNA): @@ -185,8 +229,18 @@ class NanoVNA_F(NanoVNA): class Version: def __init__(self, version_string): self.version_string = version_string - results = re.match(r"(version )?(\d+)\.(\d+)\.(\d+\w+)", version_string) + results = re.match(r"(\D+)?\s*(\d+)\.(\d+)\.(\d+)(\w*)", version_string) if results: - self.major = results.group(1) - self.minor = results.group(2) - self.revision = results.group(3) + self.major = int(results.group(2)) + self.minor = int(results.group(3)) + self.revision = int(results.group(4)) + self.note = results.group(5) + logger.debug("Parsed version as %d.%d.%d%s", self.major, self.minor, self.revision, self.note) + + @staticmethod + def getVersion(major: int, minor: int, revision: int, note=""): + return Version(str(major) + "." + str(minor) + "." + str(revision) + note) + + def __gt__(self, other: "Version"): + return self.major > other.major or self.major == other.major and self.minor > other.minor or \ + self.major == other.major and self.minor == other.minor and self.revision > other.revision diff --git a/NanoVNASaver/SweepWorker.py b/NanoVNASaver/SweepWorker.py index df31cd2d..324143ef 100644 --- a/NanoVNASaver/SweepWorker.py +++ b/NanoVNASaver/SweepWorker.py @@ -159,7 +159,8 @@ def run(self): logger.debug("Resetting NanoVNA sweep to full range: %d to %d", NanoVNASaver.parseFrequency(self.app.sweepStartInput.text()), NanoVNASaver.parseFrequency(self.app.sweepEndInput.text())) - self.vna.setSweep(NanoVNASaver.parseFrequency(self.app.sweepStartInput.text()), NanoVNASaver.parseFrequency(self.app.sweepEndInput.text())) + self.vna.resetSweep(NanoVNASaver.parseFrequency(self.app.sweepStartInput.text()), + NanoVNASaver.parseFrequency(self.app.sweepEndInput.text())) self.percentage = 100 logger.debug("Sending \"finished\" signal") @@ -274,8 +275,6 @@ def truncate(self, values: List[List[tuple]], count): def readSegment(self, start, stop): logger.debug("Setting sweep range to %d to %d", start, stop) self.vna.setSweep(start, stop) - sleep(1) # TODO This long delay seems to fix the weird data transitions we were seeing by getting partial - # sweeps. Clearly something needs to be done, maybe at firmware level, to address this fully. # Let's check the frequencies first: frequencies = self.readFreq() @@ -341,7 +340,7 @@ def readFreq(self): while not done: done = True returnfreq = [] - tmpfreq = self.vna.readValues("frequencies") + tmpfreq = self.vna.readFrequencies() if not tmpfreq: logger.warning("Read no frequencies") raise NanoVNAValueException("Failed reading frequencies: Returned no values.") From 5d3b159e130f06c41d1d1e256a4b74d2a60c9271 Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Tue, 8 Oct 2019 23:11:13 +0200 Subject: [PATCH 05/21] - Fixed a division by zero when updating step size --- NanoVNASaver/NanoVNASaver.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index a5effba7..b60c9e0f 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -839,8 +839,9 @@ def updateStepSize(self): return if self.sweepCountInput.text().isdigit(): segments = int(self.sweepCountInput.text()) - fstep = fspan / (segments * 101) - self.sweepStepLabel.setText(self.formatShortFrequency(fstep) + "/step") + if segments > 0: + fstep = fspan / (segments * 101) + self.sweepStepLabel.setText(self.formatShortFrequency(fstep) + "/step") @staticmethod def formatFrequency(freq): From 4d4b9c3601ce2e3651bfce77bb3171278edf11b6 Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Tue, 8 Oct 2019 23:16:22 +0200 Subject: [PATCH 06/21] - Fixed truncated averages never truncating by more than 1 value --- NanoVNASaver/SweepWorker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NanoVNASaver/SweepWorker.py b/NanoVNASaver/SweepWorker.py index 324143ef..ba0c7d22 100644 --- a/NanoVNASaver/SweepWorker.py +++ b/NanoVNASaver/SweepWorker.py @@ -252,10 +252,10 @@ def truncate(self, values: List[List[tuple]], count): for valueset in values: avg = np.average(valueset, 0) # avg becomes a 2-value array of the location of the average + new_valueset = valueset for n in range(count): max_deviance = 0 max_idx = -1 - new_valueset = valueset for i in range(len(new_valueset)): deviance = abs(new_valueset[i][0] - avg[0])**2 + abs(new_valueset[i][1] - avg[1])**2 if deviance > max_deviance: From aaaf778f89890c17ff5b74ac48afa419094ef6ca Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 9 Oct 2019 11:30:29 +0200 Subject: [PATCH 07/21] - Changed load/save of touchstone files to select files via system dialogs - Cal files should now save with .cal extension --- NanoVNASaver/Calibration.py | 11 +++- NanoVNASaver/NanoVNASaver.py | 110 ++++++++++++++++------------------- 2 files changed, 59 insertions(+), 62 deletions(-) diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index 6b54eac9..6fb863ae 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -475,7 +475,16 @@ def saveCalibration(self): return filedialog = QtWidgets.QFileDialog(self) filedialog.setDefaultSuffix("cal") - filename, _ = filedialog.getSaveFileName(filter="Calibration Files (*.cal);;All files (*.*)") + filedialog.setNameFilter("Calibration Files (*.cal);;All files (*.*)") + filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) + selected = filedialog.exec() + if selected: + filename = filedialog.selectedFiles()[0] + else: + return + if filename == "": + logger.debug("No file name selected.") + return self.app.calibration.notes = self.notes_textedit.toPlainText().splitlines() if filename and self.app.calibration.saveCalibration(filename): self.app.settings.setValue("CalibrationFile", filename) diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index b60c9e0f..d959489f 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -401,53 +401,37 @@ def __init__(self): self.fileWindow = QtWidgets.QWidget() self.fileWindow.setWindowTitle("Files") self.fileWindow.setWindowIcon(self.icon) + self.fileWindow.setMinimumWidth(200) shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self.fileWindow, self.fileWindow.hide) file_window_layout = QtWidgets.QVBoxLayout() self.fileWindow.setLayout(file_window_layout) - reference_file_control_box = QtWidgets.QGroupBox("Import file") - reference_file_control_layout = QtWidgets.QFormLayout(reference_file_control_box) - self.referenceFileNameInput = QtWidgets.QLineEdit("") - btn_reference_file_picker = QtWidgets.QPushButton("...") - btn_reference_file_picker.setMaximumWidth(25) - btn_reference_file_picker.clicked.connect(self.pickReferenceFile) - reference_file_name_layout = QtWidgets.QHBoxLayout() - reference_file_name_layout.addWidget(self.referenceFileNameInput) - reference_file_name_layout.addWidget(btn_reference_file_picker) + load_file_control_box = QtWidgets.QGroupBox("Import file") + load_file_control_box.setMaximumWidth(300) + load_file_control_layout = QtWidgets.QFormLayout(load_file_control_box) - reference_file_control_layout.addRow(QtWidgets.QLabel("Filename"), reference_file_name_layout) - file_window_layout.addWidget(reference_file_control_box) - - btn_load_reference = QtWidgets.QPushButton("Load reference") - btn_load_reference.clicked.connect(self.loadReferenceFile) btn_load_sweep = QtWidgets.QPushButton("Load as sweep") btn_load_sweep.clicked.connect(self.loadSweepFile) - reference_file_control_layout.addRow(btn_load_reference) - reference_file_control_layout.addRow(btn_load_sweep) + btn_load_reference = QtWidgets.QPushButton("Load reference") + btn_load_reference.clicked.connect(self.loadReferenceFile) + load_file_control_layout.addRow(btn_load_sweep) + load_file_control_layout.addRow(btn_load_reference) - file_control_box = QtWidgets.QGroupBox() - file_control_box.setTitle("Export file") - file_control_box.setMaximumWidth(300) - file_control_layout = QtWidgets.QFormLayout(file_control_box) - self.fileNameInput = QtWidgets.QLineEdit("") - btn_file_picker = QtWidgets.QPushButton("...") - btn_file_picker.setMaximumWidth(25) - btn_file_picker.clicked.connect(self.pickFile) - file_name_layout = QtWidgets.QHBoxLayout() - file_name_layout.addWidget(self.fileNameInput) - file_name_layout.addWidget(btn_file_picker) + file_window_layout.addWidget(load_file_control_box) - file_control_layout.addRow(QtWidgets.QLabel("Filename"), file_name_layout) + save_file_control_box = QtWidgets.QGroupBox("Export file") + save_file_control_box.setMaximumWidth(300) + save_file_control_layout = QtWidgets.QFormLayout(save_file_control_box) - self.btnExportFile = QtWidgets.QPushButton("Export data S1P") - self.btnExportFile.clicked.connect(self.exportFileS1P) - file_control_layout.addRow(self.btnExportFile) + btnExportFile = QtWidgets.QPushButton("Save file (S1P)") + btnExportFile.clicked.connect(self.exportFileS1P) + save_file_control_layout.addRow(btnExportFile) - self.btnExportFile = QtWidgets.QPushButton("Export data S2P") - self.btnExportFile.clicked.connect(self.exportFileS2P) - file_control_layout.addRow(self.btnExportFile) + btnExportFile = QtWidgets.QPushButton("Save file (S2P)") + btnExportFile.clicked.connect(self.exportFileS2P) + save_file_control_layout.addRow(btnExportFile) - file_window_layout.addWidget(file_control_box) + file_window_layout.addWidget(save_file_control_box) btn_open_file_window = QtWidgets.QPushButton("Files ...") btn_open_file_window.clicked.connect(self.displayFileWindow) @@ -509,26 +493,22 @@ def getPort() -> str: return port return "" - def pickReferenceFile(self): - filename, _ = QtWidgets.QFileDialog.getOpenFileName(directory=self.referenceFileNameInput.text(), - filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") - if filename != "": - self.referenceFileNameInput.setText(filename) - - def pickFile(self): - filename, _ = QtWidgets.QFileDialog.getSaveFileName(directory=self.fileNameInput.text(), - filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") - if filename != "": - self.fileNameInput.setText(filename) - def exportFileS1P(self): - logger.debug("Save S1P file to %s", self.fileNameInput.text()) - if len(self.data) == 0: - logger.warning("No data stored, nothing written.") + filedialog = QtWidgets.QFileDialog(self) + filedialog.setDefaultSuffix("s1p") + filedialog.setNameFilter("Touchstone Files (*.s1p *.s2p);;All files (*.*)") + filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) + selected = filedialog.exec() + if selected: + filename = filedialog.selectedFiles()[0] + else: return - filename = self.fileNameInput.text() if filename == "": - logger.warning("No filename entered, nothing saved.") + logger.debug("No file name selected.") + return + logger.debug("Save S1P file to %s", filename) + if len(self.data) == 0: + logger.warning("No data stored, nothing written.") return try: logger.debug("Opening %s for writing", filename) @@ -545,13 +525,21 @@ def exportFileS1P(self): return def exportFileS2P(self): - logger.debug("Save S2P file to %s", self.fileNameInput.text()) - if len(self.data) == 0 or len(self.data21) == 0: - logger.warning("No data stored, nothing written.") + filedialog = QtWidgets.QFileDialog(self) + filedialog.setDefaultSuffix("s2p") + filedialog.setNameFilter("Touchstone Files (*.s1p *.s2p);;All files (*.*)") + filedialog.setAcceptMode(QtWidgets.QFileDialog.AcceptSave) + selected = filedialog.exec() + if selected: + filename = filedialog.selectedFiles()[0] + else: return - filename = self.fileNameInput.text() if filename == "": - logger.warning("No filename entered, nothing saved.") + logger.debug("No file name selected.") + return + logger.debug("Save S2P file to %s", filename) + if len(self.data) == 0 or len(self.data21) == 0: + logger.warning("No data stored, nothing written.") return try: logger.debug("Opening %s for writing", filename) @@ -955,16 +943,16 @@ def resetReference(self): self.btnResetReference.setDisabled(True) def loadReferenceFile(self): - filename = self.referenceFileNameInput.text() - if filename is not "": + filename, _ = QtWidgets.QFileDialog.getOpenFileName(filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") + if filename != "": self.resetReference() t = Touchstone(filename) t.load() self.setReference(t.s11data, t.s21data, filename) def loadSweepFile(self): - filename = self.referenceFileNameInput.text() - if filename is not "": + filename, _ = QtWidgets.QFileDialog.getOpenFileName(filter="Touchstone Files (*.s1p *.s2p);;All files (*.*)") + if filename != "": self.data = [] self.data21 = [] t = Touchstone(filename) From 10602b341969d0b618c13cb2dc5fd1894aee144d Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 9 Oct 2019 12:14:55 +0200 Subject: [PATCH 08/21] - Version numbers are more complex than expected. --- NanoVNASaver/Hardware.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index c21c7fb8..60eab140 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -229,7 +229,7 @@ class NanoVNA_F(NanoVNA): class Version: def __init__(self, version_string): self.version_string = version_string - results = re.match(r"(\D+)?\s*(\d+)\.(\d+)\.(\d+)(\w*)", version_string) + results = re.match(r"(\D+)?\s*(\d+)\.(\d+)\.(\d+)(.*)", version_string) if results: self.major = int(results.group(2)) self.minor = int(results.group(3)) From d92f799fc3bcae56e179ac241e94bf587e33ae9d Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 9 Oct 2019 14:08:48 +0200 Subject: [PATCH 09/21] - New about dialogue, with link to the software homepage. --- NanoVNASaver/Hardware.py | 7 ++++ NanoVNASaver/NanoVNASaver.py | 77 +++++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index 60eab140..ce16f314 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -31,6 +31,7 @@ def __init__(self, app, serialPort: serial.Serial): from NanoVNASaver.NanoVNASaver import NanoVNASaver self.app: NanoVNASaver = app self.serial = serialPort + self.version: Version = Version("0.0.0") @staticmethod def getVNA(app, serialPort: serial.Serial) -> 'VNA': @@ -54,6 +55,9 @@ def readValues21(self) -> List[str]: def resetSweep(self, start: int, stop: int): pass + def isValid(self): + return False + def flushSerialBuffers(self): if self.app.serialLock.acquire(): self.serial.write(b"\r\n\r\n") @@ -176,6 +180,9 @@ def __init__(self, app, serialPort): logger.debug("Older than 0.2.0, using old sweep command.") self.useScan = False + def isValid(self): + return True + def readFrequencies(self) -> List[str]: return self.readValues("frequencies") diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index d959489f..4c638329 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -28,7 +28,7 @@ from PyQt5.QtCore import QModelIndex from serial.tools import list_ports -from NanoVNASaver.Hardware import VNA, InvalidVNA +from NanoVNASaver.Hardware import VNA, InvalidVNA, Version from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagChart, QualityFactorChart, TDRChart, \ RealImaginaryChart from .Calibration import CalibrationWindow, Calibration @@ -63,7 +63,7 @@ def __init__(self): "NanoVNASaver", "NanoVNASaver") print("Settings: " + self.settings.fileName()) self.threadpool = QtCore.QThreadPool() - self.vna = InvalidVNA() + self.vna: VNA = InvalidVNA() self.worker = SweepWorker(self, self.vna) self.bands = BandsModel() @@ -453,15 +453,12 @@ def __init__(self): self.displaySetupWindow = DisplaySettingsWindow(self) btn_display_setup.clicked.connect(self.displaySettingsWindow) + self.aboutWindow = AboutWindow(self) + btn_about = QtWidgets.QPushButton("About ...") btn_about.setMaximumWidth(250) - btn_about.clicked.connect(lambda: QtWidgets.QMessageBox.about(self, "About NanoVNASaver", - "NanoVNASaver version " - + NanoVNASaver.version + - "\n\n\N{COPYRIGHT SIGN} Copyright 2019 Rune B. Broberg\n" + - "This program comes with ABSOLUTELY NO WARRANTY\n" + - "This program is licensed under the GNU General Public License version 3\n\n" + - "See https://mihtjel.github.io/nanovna-saver/ for further details")) + + btn_about.clicked.connect(self.displayAboutWindow) button_grid = QtWidgets.QGridLayout() button_grid.addWidget(btn_open_file_window, 0, 0) @@ -987,6 +984,10 @@ def displayAnalysisWindow(self): self.analysis_window.show() QtWidgets.QApplication.setActiveWindow(self.analysis_window) + def displayAboutWindow(self): + self.aboutWindow.show() + QtWidgets.QApplication.setActiveWindow(self.aboutWindow) + def showError(self, text): error_message = QtWidgets.QErrorMessage(self) error_message.showMessage(text) @@ -1401,6 +1402,64 @@ def displayBandsWindow(self): QtWidgets.QApplication.setActiveWindow(self.bandsWindow) +class AboutWindow(QtWidgets.QWidget): + def __init__(self, app: NanoVNASaver): + super().__init__() + self.app = app + + self.setWindowTitle("About NanoVNASaver") + self.setWindowIcon(self.app.icon) + top_layout = QtWidgets.QHBoxLayout() + self.setLayout(top_layout) + #self.setAutoFillBackground(True) + pal = self.palette() + pal.setColor(QtGui.QPalette.Background, QtGui.QColor("white")) + self.setPalette(pal) + shortcut = QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) + + icon_layout = QtWidgets.QVBoxLayout() + top_layout.addLayout(icon_layout) + icon = QtWidgets.QLabel() + icon.setPixmap(self.app.icon.pixmap(128, 128)) + icon_layout.addWidget(icon) + icon_layout.addStretch() + + layout = QtWidgets.QVBoxLayout() + top_layout.addLayout(layout) + + layout.addWidget(QtWidgets.QLabel("NanoVNASaver version " + NanoVNASaver.version)) + layout.addWidget(QtWidgets.QLabel("")) + layout.addWidget(QtWidgets.QLabel("\N{COPYRIGHT SIGN} Copyright 2019 Rune B. Broberg")) + layout.addWidget(QtWidgets.QLabel("This program comes with ABSOLUTELY NO WARRANTY")) + layout.addWidget(QtWidgets.QLabel("This program is licensed under the GNU General Public License version 3")) + layout.addWidget(QtWidgets.QLabel("")) + link_label = QtWidgets.QLabel("For further details, see: " + + "" + + "https://mihtjel.github.io/nanovna-saver/") + link_label.setOpenExternalLinks(True) + layout.addWidget(link_label) + layout.addWidget(QtWidgets.QLabel("")) + + self.versionLabel = QtWidgets.QLabel("NanoVNA Firmware Version: Not connected.") + layout.addWidget(self.versionLabel) + + layout.addStretch() + + btn_ok = QtWidgets.QPushButton("Ok") + btn_ok.clicked.connect(lambda: self.close()) + layout.addWidget(btn_ok) + + def show(self): + super().show() + self.updateLabels() + + def updateLabels(self): + if self.app.vna.isValid(): + logger.debug("Valid VNA") + v: Version = self.app.vna.version + self.versionLabel.setText("NanoVNA Firmware Version: " + v.version_string) + + class TDRWindow(QtWidgets.QWidget): def __init__(self, app: NanoVNASaver): super().__init__() From fd72256ce4bbccf651a8cf09fc4ea479f8284fab Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 9 Oct 2019 14:18:03 +0200 Subject: [PATCH 10/21] Added -6 dB bandwidth for bandpass filters. Renamed span to bandwidth. --- NanoVNASaver/Analysis.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NanoVNASaver/Analysis.py b/NanoVNASaver/Analysis.py index b8cad518..a9e2bdfd 100644 --- a/NanoVNASaver/Analysis.py +++ b/NanoVNASaver/Analysis.py @@ -364,11 +364,13 @@ def __init__(self, app): self.center_frequency_label = QtWidgets.QLabel() self.span_label = QtWidgets.QLabel() + self.six_db_span_label = QtWidgets.QLabel() self.quality_label = QtWidgets.QLabel() layout.addRow("Center frequency:", self.center_frequency_label) - layout.addRow("Span:", self.span_label) + layout.addRow("Bandwidth (-3 dB):", self.span_label) layout.addRow("Quality factor:", self.quality_label) + layout.addRow("Bandwidth (-6 dB):", self.six_db_span_label) layout.addRow(QtWidgets.QLabel("")) @@ -583,6 +585,10 @@ def runAnalysis(self): upper_six_db_cutoff_frequency = self.app.data21[upper_six_db_location].freq self.upper_six_db_label.setText(NanoVNASaver.formatFrequency(upper_six_db_cutoff_frequency)) + six_db_span = upper_six_db_cutoff_frequency - lower_six_db_cutoff_frequency + + self.six_db_span_label.setText(NanoVNASaver.formatFrequency(six_db_span)) + upper_six_db_attenuation = NanoVNASaver.gain(self.app.data21[upper_six_db_location]) upper_max_attenuation = NanoVNASaver.gain(self.app.data21[len(self.app.data21)-1]) frequency_factor = upper_six_db_cutoff_frequency / self.app.data21[len(self.app.data21)-1].freq From 3027105af302233b32445ef258ad610cd1b41545 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 9 Oct 2019 14:46:08 +0200 Subject: [PATCH 11/21] - Prevent applying a calibration while a sweep is running. --- NanoVNASaver/Calibration.py | 5 +++++ NanoVNASaver/NanoVNASaver.py | 15 +++++++-------- NanoVNASaver/SweepWorker.py | 9 ++++++--- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/NanoVNASaver/Calibration.py b/NanoVNASaver/Calibration.py index 6fb863ae..8574c2cc 100644 --- a/NanoVNASaver/Calibration.py +++ b/NanoVNASaver/Calibration.py @@ -417,6 +417,11 @@ def reset(self): self.notes_textedit.clear() def calculate(self): + if self.app.btnStopSweep.isEnabled(): + # Currently sweeping + self.app.showError("Unable to apply calibration while a sweep is running. " + + "Please stop the sweep and try again.") + return # TODO: Error handling for all the fields. if self.use_ideal_values.isChecked(): self.app.calibration.useIdealShort = True diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 4c638329..4b8bfde1 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -64,7 +64,11 @@ def __init__(self): print("Settings: " + self.settings.fileName()) self.threadpool = QtCore.QThreadPool() self.vna: VNA = InvalidVNA() - self.worker = SweepWorker(self, self.vna) + self.worker = SweepWorker(self) + + self.worker.signals.updated.connect(self.dataUpdated) + self.worker.signals.finished.connect(self.sweepFinished) + self.worker.signals.sweepError.connect(self.showSweepError) self.bands = BandsModel() @@ -577,11 +581,7 @@ def startSerial(self): sleep(0.05) self.vna = VNA.getVNA(self, self.serial) - self.worker = SweepWorker(self, self.vna) - - self.worker.signals.updated.connect(self.dataUpdated) - self.worker.signals.finished.connect(self.sweepFinished) - self.worker.signals.sweepError.connect(self.showSweepError) + self.worker.setVNA(self.vna) logger.info(self.vna.readFirmware()) @@ -989,8 +989,7 @@ def displayAboutWindow(self): QtWidgets.QApplication.setActiveWindow(self.aboutWindow) def showError(self, text): - error_message = QtWidgets.QErrorMessage(self) - error_message.showMessage(text) + QtWidgets.QMessageBox.warning(self, "Error", text) def showSweepError(self): self.showError(self.worker.error_message) diff --git a/NanoVNASaver/SweepWorker.py b/NanoVNASaver/SweepWorker.py index ba0c7d22..12db90a3 100644 --- a/NanoVNASaver/SweepWorker.py +++ b/NanoVNASaver/SweepWorker.py @@ -24,7 +24,7 @@ import NanoVNASaver import logging -from NanoVNASaver.Hardware import VNA +from NanoVNASaver.Hardware import VNA, InvalidVNA logger = logging.getLogger(__name__) @@ -38,12 +38,12 @@ class WorkerSignals(QtCore.QObject): class SweepWorker(QtCore.QRunnable): - def __init__(self, app: NanoVNASaver, vna: VNA): + def __init__(self, app: NanoVNASaver): super().__init__() logger.info("Initializing SweepWorker") self.signals = WorkerSignals() self.app = app - self.vna = vna + self.vna: VNA = InvalidVNA() self.noSweeps = 1 self.setAutoDelete(False) self.percentage = 0 @@ -370,6 +370,9 @@ def setAveraging(self, averaging: bool, averages: str, truncates: str): except: return + def setVNA(self, vna): + self.vna = vna + class NanoVNAValueException(Exception): pass From 11a0c38d08b1f18e062bd724a74d4a8e81e4cabf Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 9 Oct 2019 15:10:47 +0200 Subject: [PATCH 12/21] - Display NanoVNA type in the About window --- NanoVNASaver/Hardware.py | 18 ++++++++++++++++-- NanoVNASaver/NanoVNASaver.py | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index ce16f314..0eb0895b 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -27,6 +27,8 @@ class VNA: + name = "VNA" + def __init__(self, app, serialPort: serial.Serial): from NanoVNASaver.NanoVNASaver import NanoVNASaver self.app: NanoVNASaver = app @@ -39,7 +41,11 @@ def getVNA(app, serialPort: serial.Serial) -> 'VNA': tmp_vna = VNA(app, serialPort) tmp_vna.flushSerialBuffers() firmware = tmp_vna.readFirmware() - if firmware.find("NanoVNA") > 0: + if firmware.find("NanoVNA-H") > 0: + return NanoVNA_H(app, serialPort) + if firmware.find("NanoVNA-F") > 0: + return NanoVNA_F(app, serialPort) + elif firmware.find("NanoVNA") > 0: return NanoVNA(app, serialPort) return InvalidVNA(app, serialPort) @@ -136,6 +142,8 @@ def setSweep(self, start, stop): class InvalidVNA(VNA): + name = "Invalid" + def __init__(self): pass @@ -168,6 +176,8 @@ def flushSerialBuffers(self): class NanoVNA(VNA): + name = "NanoVNA" + def __init__(self, app, serialPort): super().__init__(app, serialPort) self.version = Version(self.readVersion()) @@ -229,8 +239,12 @@ def setSweep(self, start, stop): sleep(1) +class NanoVNA_H(NanoVNA): + name = "NanoVNA-H" + + class NanoVNA_F(NanoVNA): - pass + name = "NanoVNA-F" class Version: diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 4b8bfde1..c7cac152 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -1456,7 +1456,7 @@ def updateLabels(self): if self.app.vna.isValid(): logger.debug("Valid VNA") v: Version = self.app.vna.version - self.versionLabel.setText("NanoVNA Firmware Version: " + v.version_string) + self.versionLabel.setText("NanoVNA Firmware Version: " + self.app.vna.name + " " + v.version_string) class TDRWindow(QtWidgets.QWidget): From e8a12faee316b86cc644c662d64141ce2434c4fd Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 9 Oct 2019 16:08:19 +0200 Subject: [PATCH 13/21] Redid rolloff calculations, now identical for low-, high- and bandpass. --- NanoVNASaver/Analysis.py | 185 ++++++++++++++++++++++++++------------- 1 file changed, 124 insertions(+), 61 deletions(-) diff --git a/NanoVNASaver/Analysis.py b/NanoVNASaver/Analysis.py index a9e2bdfd..6bb3c4d2 100644 --- a/NanoVNASaver/Analysis.py +++ b/NanoVNASaver/Analysis.py @@ -38,6 +38,22 @@ def runAnalysis(self): def reset(self): pass + def calculateRolloff(self, location1, location2): + from NanoVNASaver.NanoVNASaver import NanoVNASaver + frequency1 = self.app.data21[location1].freq + frequency2 = self.app.data21[location2].freq + gain1 = NanoVNASaver.gain(self.app.data21[location1]) + gain2 = NanoVNASaver.gain(self.app.data21[location2]) + frequency_factor = frequency2 / frequency1 + if frequency_factor < 1: + frequency_factor = 1 / frequency_factor + attenuation = abs(gain1 - gain2) + logger.debug("Measured points: %d Hz and %d Hz", frequency1, frequency2) + logger.debug("%f dB over %f factor", attenuation, frequency_factor) + octave_attenuation = attenuation / (math.log10(frequency_factor) / math.log10(2)) + decade_attenuation = attenuation / math.log10(frequency_factor) + return octave_attenuation, decade_attenuation + class LowPassAnalysis(Analysis): def __init__(self, app): @@ -154,16 +170,21 @@ def runAnalysis(self): six_db_cutoff_frequency = self.app.data21[six_db_location].freq self.six_db_label.setText(NanoVNASaver.formatFrequency(six_db_cutoff_frequency)) - six_db_attenuation = NanoVNASaver.gain(self.app.data21[six_db_location]) - max_attenuation = NanoVNASaver.gain(self.app.data21[len(self.app.data21) - 1]) - frequency_factor = self.app.data21[len(self.app.data21) - 1].freq / six_db_cutoff_frequency - attenuation = (max_attenuation - six_db_attenuation) - logger.debug("Measured points: %d Hz and %d Hz", six_db_cutoff_frequency, self.app.data21[len(self.app.data21) - 1].freq) - logger.debug("%d dB over %f factor", attenuation, frequency_factor) - octave_attenuation = attenuation / (math.log10(frequency_factor) / math.log10(2)) - self.db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") - decade_attenuation = attenuation / math.log10(frequency_factor) - self.db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") + ten_db_location = -1 + for i in range(cutoff_location, len(self.app.data21)): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 10: + # We found 6dB location + ten_db_location = i + break + + twenty_db_location = -1 + for i in range(cutoff_location, len(self.app.data21)): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 20: + # We found 6dB location + twenty_db_location = i + break sixty_db_location = -1 for i in range(six_db_location, len(self.app.data21)): @@ -173,16 +194,23 @@ def runAnalysis(self): sixty_db_location = i break - if sixty_db_location < 0: + if sixty_db_location > 0: + sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + else: # # We derive 60 dB instead # factor = 10 * (-54 / decade_attenuation) # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.sixty_db_label.setText("Not calculated") + if ten_db_location > 0 and twenty_db_location > 0: + octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) + self.db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") + self.db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") else: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq - self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + self.db_per_octave_label.setText("Not calculated") + self.db_per_decade_label.setText("Not calculated") self.result_label.setText("Analysis complete (" + str(len(self.app.data)) + " points)") @@ -303,16 +331,21 @@ def runAnalysis(self): six_db_cutoff_frequency = self.app.data21[six_db_location].freq self.six_db_label.setText(NanoVNASaver.formatFrequency(six_db_cutoff_frequency)) - six_db_attenuation = NanoVNASaver.gain(self.app.data21[six_db_location]) - max_attenuation = NanoVNASaver.gain(self.app.data21[len(self.app.data21) - 1]) - frequency_factor = self.app.data21[len(self.app.data21) - 1].freq / six_db_cutoff_frequency - attenuation = (max_attenuation - six_db_attenuation) - logger.debug("Measured points: %d Hz and %d Hz", six_db_cutoff_frequency, self.app.data21[len(self.app.data21) - 1].freq) - logger.debug("%d dB over %f factor", attenuation, frequency_factor) - octave_attenuation = attenuation / (math.log10(frequency_factor) / math.log10(2)) - self.db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") - decade_attenuation = attenuation / math.log10(frequency_factor) - self.db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") + ten_db_location = -1 + for i in range(cutoff_location, -1, -1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 10: + # We found 6dB location + ten_db_location = i + break + + twenty_db_location = -1 + for i in range(cutoff_location, -1, -1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 20: + # We found 6dB location + twenty_db_location = i + break sixty_db_location = -1 for i in range(six_db_location, -1, -1): @@ -322,16 +355,23 @@ def runAnalysis(self): sixty_db_location = i break - if sixty_db_location < 0: + if sixty_db_location > 0: + sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + else: # # We derive 60 dB instead # factor = 10 * (-54 / decade_attenuation) # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.sixty_db_label.setText("Not calculated") + if ten_db_location > 0 and twenty_db_location > 0: + octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) + self.db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") + self.db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") else: - sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq - self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + self.db_per_octave_label.setText("Not calculated") + self.db_per_decade_label.setText("Not calculated") self.result_label.setText("Analysis complete (" + str(len(self.app.data)) + " points)") @@ -539,35 +579,47 @@ def runAnalysis(self): lower_six_db_cutoff_frequency = self.app.data21[lower_six_db_location].freq self.lower_six_db_label.setText(NanoVNASaver.formatFrequency(lower_six_db_cutoff_frequency)) - lower_six_db_attenuation = NanoVNASaver.gain(self.app.data21[lower_six_db_location]) - lower_max_attenuation = NanoVNASaver.gain(self.app.data21[0]) - frequency_factor = self.app.data21[0].freq / lower_six_db_cutoff_frequency - lower_attenuation = (lower_max_attenuation - lower_six_db_attenuation) - logger.debug("Measured points: %d Hz and %d Hz", lower_six_db_cutoff_frequency, self.app.data21[0].freq) - logger.debug("%d dB over %f factor", lower_attenuation, frequency_factor) - octave_attenuation = lower_attenuation / (math.log10(frequency_factor) / math.log10(2)) - self.lower_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") - decade_attenuation = lower_attenuation / math.log10(frequency_factor) - self.lower_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") - - lower_sixty_db_location = -1 + ten_db_location = -1 + for i in range(lower_cutoff_location, -1, -1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 10: + # We found 6dB location + ten_db_location = i + break + + twenty_db_location = -1 + for i in range(lower_cutoff_location, -1, -1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 20: + # We found 6dB location + twenty_db_location = i + break + + sixty_db_location = -1 for i in range(lower_six_db_location, -1, -1): db = NanoVNASaver.gain(self.app.data21[i]) if (pass_band_db - db) > 60: # We found 60dB location! Wow. - lower_sixty_db_location = i + sixty_db_location = i break - if lower_sixty_db_location < 0: + if sixty_db_location > 0: + sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + self.lower_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + else: # # We derive 60 dB instead # factor = 10 * (-54 / decade_attenuation) # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) - # self.upper_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") + # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.lower_sixty_db_label.setText("Not calculated") + if ten_db_location > 0 and twenty_db_location > 0: + octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) + self.lower_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") + self.lower_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") else: - lower_sixty_db_cutoff_frequency = self.app.data21[lower_sixty_db_location].freq - self.lower_sixty_db_label.setText(NanoVNASaver.formatFrequency(lower_sixty_db_cutoff_frequency)) + self.lower_db_per_octave_label.setText("Not calculated") + self.lower_db_per_decade_label.setText("Not calculated") # Upper roll-off @@ -589,36 +641,47 @@ def runAnalysis(self): self.six_db_span_label.setText(NanoVNASaver.formatFrequency(six_db_span)) - upper_six_db_attenuation = NanoVNASaver.gain(self.app.data21[upper_six_db_location]) - upper_max_attenuation = NanoVNASaver.gain(self.app.data21[len(self.app.data21)-1]) - frequency_factor = upper_six_db_cutoff_frequency / self.app.data21[len(self.app.data21)-1].freq - upper_attenuation = (upper_max_attenuation - upper_six_db_attenuation) - logger.debug("Measured points: %d Hz and %d Hz", upper_six_db_cutoff_frequency, - self.app.data21[len(self.app.data21)-1].freq) - logger.debug("%d dB over %f factor", upper_attenuation, frequency_factor) - octave_attenuation = upper_attenuation / (math.log10(frequency_factor) / math.log10(2)) - self.upper_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") - decade_attenuation = upper_attenuation / math.log10(frequency_factor) - self.upper_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") - - upper_sixty_db_location = -1 + ten_db_location = -1 + for i in range(upper_cutoff_location, len(self.app.data21), 1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 10: + # We found 6dB location + ten_db_location = i + break + + twenty_db_location = -1 + for i in range(upper_cutoff_location, len(self.app.data21), 1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 20: + # We found 6dB location + twenty_db_location = i + break + + sixty_db_location = -1 for i in range(upper_six_db_location, len(self.app.data21), 1): db = NanoVNASaver.gain(self.app.data21[i]) if (pass_band_db - db) > 60: # We found 60dB location! Wow. - upper_sixty_db_location = i + sixty_db_location = i break - if upper_sixty_db_location < 0: + if sixty_db_location > 0: + sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + self.upper_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + else: # # We derive 60 dB instead # factor = 10 * (-54 / decade_attenuation) # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) - # self.upper_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") + # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.upper_sixty_db_label.setText("Not calculated") + if ten_db_location > 0 and twenty_db_location > 0: + octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) + self.upper_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") + self.upper_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") else: - upper_sixty_db_cutoff_frequency = self.app.data21[upper_sixty_db_location].freq - self.upper_sixty_db_label.setText(NanoVNASaver.formatFrequency(upper_sixty_db_cutoff_frequency)) + self.upper_db_per_octave_label.setText("Not calculated") + self.upper_db_per_decade_label.setText("Not calculated") if upper_cutoff_gain < -4 or lower_cutoff_gain < -4: self.result_label.setText("Analysis complete (" + str(len(self.app.data)) + " points)\n" + From b4c990df3f23bacaa002d784b5ba957795dabb65 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 9 Oct 2019 16:31:04 +0200 Subject: [PATCH 14/21] Bandstop filter analysis. --- NanoVNASaver/Analysis.py | 277 +++++++++++++++++++++++++++++++++++ NanoVNASaver/NanoVNASaver.py | 3 +- 2 files changed, 279 insertions(+), 1 deletion(-) diff --git a/NanoVNASaver/Analysis.py b/NanoVNASaver/Analysis.py index 6bb3c4d2..71750ff0 100644 --- a/NanoVNASaver/Analysis.py +++ b/NanoVNASaver/Analysis.py @@ -432,6 +432,10 @@ def __init__(self, app): def reset(self): self.result_label.clear() + self.span_label.clear() + self.quality_label.clear() + self.six_db_span_label.clear() + self.upper_cutoff_label.clear() self.upper_six_db_label.clear() self.upper_sixty_db_label.clear() @@ -688,3 +692,276 @@ def runAnalysis(self): "Insufficient data for analysis. Increase segment count.") else: self.result_label.setText("Analysis complete (" + str(len(self.app.data)) + " points)") + + +class BandStopAnalysis(Analysis): + def __init__(self, app): + super().__init__(app) + + self._widget = QtWidgets.QWidget() + + layout = QtWidgets.QFormLayout() + self._widget.setLayout(layout) + layout.addRow(QtWidgets.QLabel("Band stop filter analysis")) + self.result_label = QtWidgets.QLabel() + self.lower_cutoff_label = QtWidgets.QLabel() + self.lower_six_db_label = QtWidgets.QLabel() + self.lower_sixty_db_label = QtWidgets.QLabel() + self.lower_db_per_octave_label = QtWidgets.QLabel() + self.lower_db_per_decade_label = QtWidgets.QLabel() + + self.upper_cutoff_label = QtWidgets.QLabel() + self.upper_six_db_label = QtWidgets.QLabel() + self.upper_sixty_db_label = QtWidgets.QLabel() + self.upper_db_per_octave_label = QtWidgets.QLabel() + self.upper_db_per_decade_label = QtWidgets.QLabel() + layout.addRow("Result:", self.result_label) + + layout.addRow(QtWidgets.QLabel("")) + + self.center_frequency_label = QtWidgets.QLabel() + self.span_label = QtWidgets.QLabel() + self.six_db_span_label = QtWidgets.QLabel() + self.quality_label = QtWidgets.QLabel() + + layout.addRow("Center frequency:", self.center_frequency_label) + layout.addRow("Bandwidth (-3 dB):", self.span_label) + layout.addRow("Quality factor:", self.quality_label) + layout.addRow("Bandwidth (-6 dB):", self.six_db_span_label) + + layout.addRow(QtWidgets.QLabel("")) + + layout.addRow(QtWidgets.QLabel("Lower side:")) + layout.addRow("Cutoff frequency:", self.lower_cutoff_label) + layout.addRow("-6 dB point:", self.lower_six_db_label) + layout.addRow("-60 dB point:", self.lower_sixty_db_label) + layout.addRow("Roll-off:", self.lower_db_per_octave_label) + layout.addRow("Roll-off:", self.lower_db_per_decade_label) + + layout.addRow(QtWidgets.QLabel("")) + + layout.addRow(QtWidgets.QLabel("Upper side:")) + layout.addRow("Cutoff frequency:", self.upper_cutoff_label) + layout.addRow("-6 dB point:", self.upper_six_db_label) + layout.addRow("-60 dB point:", self.upper_sixty_db_label) + layout.addRow("Roll-off:", self.upper_db_per_octave_label) + layout.addRow("Roll-off:", self.upper_db_per_decade_label) + + def reset(self): + self.result_label.clear() + self.span_label.clear() + self.quality_label.clear() + self.six_db_span_label.clear() + + self.upper_cutoff_label.clear() + self.upper_six_db_label.clear() + self.upper_sixty_db_label.clear() + self.upper_db_per_octave_label.clear() + self.upper_db_per_decade_label.clear() + + self.lower_cutoff_label.clear() + self.lower_six_db_label.clear() + self.lower_sixty_db_label.clear() + self.lower_db_per_octave_label.clear() + self.lower_db_per_decade_label.clear() + + def runAnalysis(self): + from NanoVNASaver.NanoVNASaver import NanoVNASaver + self.reset() + + if len(self.app.data21) == 0: + logger.debug("No data to analyse") + self.result_label.setText("No data to analyse.") + return + + peak_location = -1 + peak_db = NanoVNASaver.gain(self.app.data21[0]) + for i in range(len(self.app.data21)): + db = NanoVNASaver.gain(self.app.data21[i]) + if db > peak_db: + peak_db = db + peak_location = i + + logger.debug("Found peak of %f at %d", peak_db, self.app.data[peak_location].freq) + + lower_cutoff_location = -1 + pass_band_db = peak_db + for i in range(len(self.app.data21)): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 3: + # We found the cutoff location + lower_cutoff_location = i + break + + lower_cutoff_frequency = self.app.data21[lower_cutoff_location].freq + lower_cutoff_gain = NanoVNASaver.gain(self.app.data21[lower_cutoff_location]) - pass_band_db + + if lower_cutoff_gain < -4: + logger.debug("Lower cutoff frequency found at %f dB - insufficient data points for true -3 dB point.", + lower_cutoff_gain) + + logger.debug("Found true lower cutoff frequency at %d", lower_cutoff_frequency) + + self.lower_cutoff_label.setText(NanoVNASaver.formatFrequency(lower_cutoff_frequency) + + " (" + str(round(lower_cutoff_gain, 1)) + " dB)") + + self.app.markers[1].setFrequency(str(lower_cutoff_frequency)) + self.app.markers[1].frequencyInput.setText(str(lower_cutoff_frequency)) + + upper_cutoff_location = -1 + for i in range(len(self.app.data21)-1, -1, -1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 3: + # We found the cutoff location + upper_cutoff_location = i + break + + upper_cutoff_frequency = self.app.data21[upper_cutoff_location].freq + upper_cutoff_gain = NanoVNASaver.gain(self.app.data21[upper_cutoff_location]) - pass_band_db + if upper_cutoff_gain < -4: + logger.debug("Upper cutoff frequency found at %f dB - insufficient data points for true -3 dB point.", + upper_cutoff_gain) + + logger.debug("Found true upper cutoff frequency at %d", upper_cutoff_frequency) + + self.upper_cutoff_label.setText(NanoVNASaver.formatFrequency(upper_cutoff_frequency) + + " (" + str(round(upper_cutoff_gain, 1)) + " dB)") + self.app.markers[2].setFrequency(str(upper_cutoff_frequency)) + self.app.markers[2].frequencyInput.setText(str(upper_cutoff_frequency)) + + span = upper_cutoff_frequency - lower_cutoff_frequency + center_frequency = math.sqrt(lower_cutoff_frequency * upper_cutoff_frequency) + q = center_frequency / span + + self.span_label.setText(NanoVNASaver.formatFrequency(span)) + self.center_frequency_label.setText(NanoVNASaver.formatFrequency(center_frequency)) + self.quality_label.setText(str(round(q, 2))) + + self.app.markers[0].setFrequency(str(round(center_frequency))) + self.app.markers[0].frequencyInput.setText(str(round(center_frequency))) + + # Lower roll-off + + lower_six_db_location = -1 + for i in range(lower_cutoff_location, len(self.app.data21)): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 6: + # We found 6dB location + lower_six_db_location = i + break + + if lower_six_db_location < 0: + self.result_label.setText("Lower 6 dB location not found.") + return + lower_six_db_cutoff_frequency = self.app.data21[lower_six_db_location].freq + self.lower_six_db_label.setText(NanoVNASaver.formatFrequency(lower_six_db_cutoff_frequency)) + + ten_db_location = -1 + for i in range(lower_cutoff_location, len(self.app.data21)): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 10: + # We found 6dB location + ten_db_location = i + break + + twenty_db_location = -1 + for i in range(lower_cutoff_location, len(self.app.data21)): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 20: + # We found 6dB location + twenty_db_location = i + break + + sixty_db_location = -1 + for i in range(lower_six_db_location, len(self.app.data21)): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 60: + # We found 60dB location! Wow. + sixty_db_location = i + break + + if sixty_db_location > 0: + sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + self.lower_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + else: + # # We derive 60 dB instead + # factor = 10 * (-54 / decade_attenuation) + # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) + # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") + self.lower_sixty_db_label.setText("Not calculated") + + if ten_db_location > 0 and twenty_db_location > 0: + octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) + self.lower_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") + self.lower_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") + else: + self.lower_db_per_octave_label.setText("Not calculated") + self.lower_db_per_decade_label.setText("Not calculated") + + # Upper roll-off + + upper_six_db_location = -1 + for i in range(upper_cutoff_location, -1, -1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 6: + # We found 6dB location + upper_six_db_location = i + break + + if upper_six_db_location < 0: + self.result_label.setText("Upper 6 dB location not found.") + return + upper_six_db_cutoff_frequency = self.app.data21[upper_six_db_location].freq + self.upper_six_db_label.setText(NanoVNASaver.formatFrequency(upper_six_db_cutoff_frequency)) + + six_db_span = upper_six_db_cutoff_frequency - lower_six_db_cutoff_frequency + + self.six_db_span_label.setText(NanoVNASaver.formatFrequency(six_db_span)) + + ten_db_location = -1 + for i in range(upper_cutoff_location, -1, -1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 10: + # We found 6dB location + ten_db_location = i + break + + twenty_db_location = -1 + for i in range(upper_cutoff_location, -1, -1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 20: + # We found 6dB location + twenty_db_location = i + break + + sixty_db_location = -1 + for i in range(upper_six_db_location, -1, -1): + db = NanoVNASaver.gain(self.app.data21[i]) + if (pass_band_db - db) > 60: + # We found 60dB location! Wow. + sixty_db_location = i + break + + if sixty_db_location > 0: + sixty_db_cutoff_frequency = self.app.data21[sixty_db_location].freq + self.upper_sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency)) + else: + # # We derive 60 dB instead + # factor = 10 * (-54 / decade_attenuation) + # sixty_db_cutoff_frequency = round(six_db_cutoff_frequency + six_db_cutoff_frequency * factor) + # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") + self.upper_sixty_db_label.setText("Not calculated") + + if ten_db_location > 0 and twenty_db_location > 0: + octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) + self.upper_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") + self.upper_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") + else: + self.upper_db_per_octave_label.setText("Not calculated") + self.upper_db_per_decade_label.setText("Not calculated") + + if upper_cutoff_gain < -4 or lower_cutoff_gain < -4: + self.result_label.setText("Analysis complete (" + str(len(self.app.data)) + " points)\n" + + "Insufficient data for analysis. Increase segment count.") + else: + self.result_label.setText("Analysis complete (" + str(len(self.app.data)) + " points)") diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index c7cac152..221f92d6 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -35,7 +35,7 @@ from .Marker import Marker from .SweepWorker import SweepWorker from .Touchstone import Touchstone -from .Analysis import Analysis, LowPassAnalysis, HighPassAnalysis, BandPassAnalysis +from .Analysis import Analysis, LowPassAnalysis, HighPassAnalysis, BandPassAnalysis, BandStopAnalysis from .about import version as ver Datapoint = collections.namedtuple('Datapoint', 'freq re im') @@ -1874,6 +1874,7 @@ def __init__(self, app): self.analysis_list.addItem("Low-pass filter", LowPassAnalysis(self.app)) self.analysis_list.addItem("Band-pass filter", BandPassAnalysis(self.app)) self.analysis_list.addItem("High-pass filter", HighPassAnalysis(self.app)) + self.analysis_list.addItem("Band-stop filter", BandStopAnalysis(self.app)) select_analysis_layout.addRow("Analysis type", self.analysis_list) self.analysis_list.currentIndexChanged.connect(self.updateSelection) From 02f40ef056311d0075868f1df03e93ba3360f2e6 Mon Sep 17 00:00:00 2001 From: Rune Broberg Date: Wed, 9 Oct 2019 16:53:10 +0200 Subject: [PATCH 15/21] Version comparison functions --- NanoVNASaver/Hardware.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index 0eb0895b..765a198a 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -265,3 +265,16 @@ def getVersion(major: int, minor: int, revision: int, note=""): def __gt__(self, other: "Version"): return self.major > other.major or self.major == other.major and self.minor > other.minor or \ self.major == other.major and self.minor == other.minor and self.revision > other.revision + + def __lt__(self, other: "Version"): + return other > self + + def __ge__(self, other: "Version"): + return self > other or self == other + + def __le__(self, other: "Version"): + return self < other or self == other + + def __eq__(self, other: "Version"): + return self.major == other.major and self.minor == other.minor and self.revision == other.revision and \ + self.note == other.note From b094f335c7010de31266798598823430e0e72d14 Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Wed, 9 Oct 2019 18:35:36 +0200 Subject: [PATCH 16/21] Automatically check for new versions --- NanoVNASaver/NanoVNASaver.py | 95 ++++++++++++++++++++++++++++++++++-- 1 file changed, 91 insertions(+), 4 deletions(-) diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 221f92d6..012b82ef 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -471,10 +471,6 @@ def __init__(self): button_grid.addWidget(btn_about, 1, 1) left_column.addLayout(button_grid) - ################################################################################################################ - # Right side - ################################################################################################################ - logger.debug("Finished building interface") def rescanSerialPort(self): @@ -1444,6 +1440,35 @@ def __init__(self, app: NanoVNASaver): layout.addStretch() + btn_check_version = QtWidgets.QPushButton("Check for updates") + btn_check_version.clicked.connect(self.findUpdates) + + self.updateLabel = QtWidgets.QLabel("Last checked: ") + self.updateCheckBox = QtWidgets.QCheckBox("Check for updates on startup") + + self.updateCheckBox.toggled.connect(self.updateSettings) + + check_for_updates = self.app.settings.value("CheckForUpdates", "Ask") + if check_for_updates == "Yes": + self.updateCheckBox.setChecked(True) + self.findUpdates(automatic = True) + elif check_for_updates == "No": + self.updateCheckBox.setChecked(False) + else: + logger.debug("Starting timer") + QtCore.QTimer.singleShot(2000, self.askAboutUpdates) + + update_hbox = QtWidgets.QHBoxLayout() + update_hbox.addWidget(btn_check_version) + update_form = QtWidgets.QFormLayout() + update_hbox.addLayout(update_form) + update_hbox.addStretch() + update_form.addRow(self.updateLabel) + update_form.addRow(self.updateCheckBox) + layout.addLayout(update_hbox) + + layout.addStretch() + btn_ok = QtWidgets.QPushButton("Ok") btn_ok.clicked.connect(lambda: self.close()) layout.addWidget(btn_ok) @@ -1458,6 +1483,68 @@ def updateLabels(self): v: Version = self.app.vna.version self.versionLabel.setText("NanoVNA Firmware Version: " + self.app.vna.name + " " + v.version_string) + def updateSettings(self): + if self.updateCheckBox.isChecked(): + self.app.settings.setValue("CheckForUpdates", "Yes") + else: + self.app.settings.setValue("CheckForUpdates", "No") + + def askAboutUpdates(self): + logger.debug("Asking about automatic update checks") + selection = QtWidgets.QMessageBox.question(self.app, "Enable checking for updates?", + "Would you like NanoVNA-Saver to check for updates automatically?") + if selection == QtWidgets.QMessageBox.Yes: + self.updateCheckBox.setChecked(True) + self.app.settings.setValue("CheckForUpdates", "Yes") + self.findUpdates() + elif selection == QtWidgets.QMessageBox.No: + self.updateCheckBox.setChecked(False) + self.app.settings.setValue("CheckForUpdates", "No") + QtWidgets.QMessageBox.information(self.app, "Checking for updates disabled", + "You can check for updates using the \"About\" window.") + else: + self.app.settings.setValue("CheckForUpdates", "Ask") + + def findUpdates(self, automatic = False): + from urllib import request, error + import json + update_url = "http://mihtjel.dk/nanovna-saver/latest.json" + + try: + updates = json.load(request.urlopen(update_url, timeout=3)) + latest_version = Version(updates['version']) + latest_url = updates['url'] + except error.HTTPError as e: + logger.exception("Checking for updates produced an HTTP exception: %s", e) + return + except json.JSONDecodeError as e: + logger.exception("Checking for updates provided an unparseable file: %s", e) + return + + logger.info("Latest version is " + latest_version.version_string) + this_version = Version(NanoVNASaver.version) + logger.info("This is " + this_version.version_string) + if latest_version > this_version: + logger.info("New update available: %s!", latest_version) + if automatic: + QtWidgets.QMessageBox.information(self, "Updates available", + "There is a new update for NanoVNA-Saver available!\n" + + "Version " + latest_version.version_string + "\n\n" + + "Press \"About\" to find the update.") + else: + QtWidgets.QMessageBox.information(self, "Updates available", + "There is a new update for NanoVNA-Saver available!") + self.updateLabel.setText("New version available.") + self.updateLabel.setOpenExternalLinks(True) + else: + # Probably don't show a message box, just update the screen? + # Maybe consider showing it if the user has automatic updates turned off. + # + # QtWidgets.QMessageBox.information(self, "No updates available", "There are no new updates available.") + # + self.updateLabel.setText("Last checked: " + strftime("%Y-%m-%d %H:%M:%S", localtime())) + return + class TDRWindow(QtWidgets.QWidget): def __init__(self, app: NanoVNASaver): From 8142c287d5bffbe6904322254360c8bc1ec53001 Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Wed, 9 Oct 2019 19:09:48 +0200 Subject: [PATCH 17/21] Implemented secondary colour for RealImaginary charts --- NanoVNASaver/Chart.py | 35 +++++++++++++++++++++++------------ NanoVNASaver/NanoVNASaver.py | 24 ++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/NanoVNASaver/Chart.py b/NanoVNASaver/Chart.py index b7d57a5a..87d42ef8 100644 --- a/NanoVNASaver/Chart.py +++ b/NanoVNASaver/Chart.py @@ -31,6 +31,8 @@ class Chart(QtWidgets.QWidget): secondarySweepColor = QtCore.Qt.darkMagenta referenceColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.blue) referenceColor.setAlpha(64) + secondaryReferenceColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.blue) + secondaryReferenceColor.setAlpha(64) backgroundColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.white) foregroundColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.lightGray) textColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.black) @@ -65,6 +67,10 @@ def setReferenceColor(self, color : QtGui.QColor): self.referenceColor = color self.update() + def setSecondaryReferenceColor(self, color : QtGui.QColor): + self.secondaryReferenceColor = color + self.update() + def setBackgroundColor(self, color: QtGui.QColor): self.backgroundColor = color pal = self.palette() @@ -1614,9 +1620,9 @@ def drawValues(self, qp: QtGui.QPainter): prev_y_im = self.getImYPosition(self.data[i-1]) # Real part first + line_pen.setColor(self.sweepColor) + qp.setPen(line_pen) if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): - line_pen.setColor(self.sweepColor) - qp.setPen(line_pen) qp.drawLine(x, y_re, prev_x, prev_y_re) elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re): new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re) @@ -1625,10 +1631,10 @@ def drawValues(self, qp: QtGui.QPainter): new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) qp.drawLine(prev_x, prev_y_re, new_x, new_y) - # Imag part first + # Imag part second + line_pen.setColor(self.secondarySweepColor) + qp.setPen(line_pen) if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): - line_pen.setColor(self.secondarySweepColor) - qp.setPen(line_pen) qp.drawLine(x, y_im, prev_x, prev_y_im) elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im): new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im) @@ -1639,7 +1645,7 @@ def drawValues(self, qp: QtGui.QPainter): primary_pen.setColor(self.referenceColor) line_pen.setColor(self.referenceColor) - secondary_pen.setColor(self.referenceColor) + secondary_pen.setColor(self.secondaryReferenceColor) qp.setPen(primary_pen) if len(self.reference) > 0: c = QtGui.QColor(self.referenceColor) @@ -1647,7 +1653,12 @@ def drawValues(self, qp: QtGui.QPainter): pen = QtGui.QPen(c) pen.setWidth(2) qp.setPen(pen) - qp.drawLine(20, 14, 25, 14) # Alpha might be low, so we draw twice + qp.drawLine(20, 14, 25, 14) + c = QtGui.QColor(self.secondaryReferenceColor) + c.setAlpha(255) + pen = QtGui.QPen(c) + pen.setWidth(2) + qp.setPen(pen) qp.drawLine(self.leftMargin + self.chartWidth, 14, self.leftMargin + self.chartWidth + 5, 14) for i in range(len(self.reference)): @@ -1667,10 +1678,10 @@ def drawValues(self, qp: QtGui.QPainter): prev_y_re = self.getReYPosition(self.reference[i-1]) prev_y_im = self.getImYPosition(self.reference[i-1]) + line_pen.setColor(self.secondaryReferenceColor) + qp.setPen(line_pen) # Real part first if self.isPlotable(x, y_re) and self.isPlotable(prev_x, prev_y_re): - line_pen.setColor(self.referenceColor) - qp.setPen(line_pen) qp.drawLine(x, y_re, prev_x, prev_y_re) elif self.isPlotable(x, y_re) and not self.isPlotable(prev_x, prev_y_re): new_x, new_y = self.getPlotable(x, y_re, prev_x, prev_y_re) @@ -1679,10 +1690,10 @@ def drawValues(self, qp: QtGui.QPainter): new_x, new_y = self.getPlotable(prev_x, prev_y_re, x, y_re) qp.drawLine(prev_x, prev_y_re, new_x, new_y) - # Imag part first + line_pen.setColor(self.secondaryReferenceColor) + qp.setPen(line_pen) + # Imag part second if self.isPlotable(x, y_im) and self.isPlotable(prev_x, prev_y_im): - line_pen.setColor(self.secondarySweepColor) - qp.setPen(line_pen) qp.drawLine(x, y_im, prev_x, prev_y_im) elif self.isPlotable(x, y_im) and not self.isPlotable(prev_x, prev_y_im): new_x, new_y = self.getPlotable(x, y_im, prev_x, prev_y_im) diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index 012b82ef..e4ad441e 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -1080,6 +1080,18 @@ def __init__(self, app: NanoVNASaver): display_options_layout.addRow("Reference color", self.btnReferenceColorPicker) + self.btnSecondaryReferenceColorPicker = QtWidgets.QPushButton("█") + self.btnSecondaryReferenceColorPicker.setFixedWidth(20) + self.secondaryReferenceColor = self.app.settings.value("SecondaryReferenceColor", + defaultValue=QtGui.QColor(0, 0, 255, 32), + type=QtGui.QColor) + self.setSecondaryReferenceColor(self.secondaryReferenceColor) + self.btnSecondaryReferenceColorPicker.clicked.connect(lambda: self.setSecondaryReferenceColor( + QtWidgets.QColorDialog.getColor(self.secondaryReferenceColor, + options=QtWidgets.QColorDialog.ShowAlphaChannel))) + + display_options_layout.addRow("Second reference color", self.btnSecondaryReferenceColorPicker) + layout.addWidget(display_options_box) color_options_box = QtWidgets.QGroupBox("Chart colors") @@ -1377,6 +1389,18 @@ def setReferenceColor(self, color): for c in self.app.charts: c.setReferenceColor(color) + def setSecondaryReferenceColor(self, color): + if color.isValid(): + self.secondaryReferenceColor = color + p = self.btnSecondaryReferenceColorPicker.palette() + p.setColor(QtGui.QPalette.ButtonText, color) + self.btnSecondaryReferenceColorPicker.setPalette(p) + self.app.settings.setValue("SecondaryReferenceColor", color) + self.app.settings.sync() + + for c in self.app.charts: + c.setSecondaryReferenceColor(color) + def setShowBands(self, show_bands): self.app.bands.enabled = show_bands self.app.bands.settings.setValue("ShowBands", show_bands) From 96e1c71e10defa5027e875b97729d0d366252d9c Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Wed, 9 Oct 2019 20:07:06 +0200 Subject: [PATCH 18/21] Improved version parsing --- NanoVNASaver/Hardware.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index 765a198a..9042e5ce 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -183,7 +183,7 @@ def __init__(self, app, serialPort): self.version = Version(self.readVersion()) logger.debug("Testing against 0.2.0") - if self.version > Version("0.2.0"): + if self.version >= Version("0.2.0"): logger.debug("Newer than 0.2.0, using new scan command.") self.useScan = True else: @@ -248,9 +248,15 @@ class NanoVNA_F(NanoVNA): class Version: + major = 0 + minor = 0 + revision = 0 + note = "" + version_string ="" + def __init__(self, version_string): self.version_string = version_string - results = re.match(r"(\D+)?\s*(\d+)\.(\d+)\.(\d+)(.*)", version_string) + results = re.match(r"(.*\D+)?(\d+)\.(\d+)\.(\d+)(.*)", version_string) if results: self.major = int(results.group(2)) self.minor = int(results.group(3)) From 1038a7c5c863fd1918d00939773a26ff2e498edd Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Wed, 9 Oct 2019 20:27:02 +0200 Subject: [PATCH 19/21] Detect incompatible scan commands --- NanoVNASaver/Hardware.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/NanoVNASaver/Hardware.py b/NanoVNASaver/Hardware.py index 9042e5ce..c03d974d 100644 --- a/NanoVNASaver/Hardware.py +++ b/NanoVNASaver/Hardware.py @@ -183,7 +183,10 @@ def __init__(self, app, serialPort): self.version = Version(self.readVersion()) logger.debug("Testing against 0.2.0") - if self.version >= Version("0.2.0"): + if self.version.version_string.find("extended with scan") > 0: + logger.debug("Incompatible scan command detected.") + self.useScan = False + elif self.version >= Version("0.2.0"): logger.debug("Newer than 0.2.0, using new scan command.") self.useScan = True else: From 5e931ad3dcf0341bdff540e6a317ac4a4a9b2093 Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Fri, 11 Oct 2019 14:16:51 +0200 Subject: [PATCH 20/21] Avoid crash if 10 dB location and 20 dB location end up in the same place. --- NanoVNASaver/Analysis.py | 14 ++++++++------ NanoVNASaver/NanoVNASaver.py | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/NanoVNASaver/Analysis.py b/NanoVNASaver/Analysis.py index 71750ff0..ad895420 100644 --- a/NanoVNASaver/Analysis.py +++ b/NanoVNASaver/Analysis.py @@ -40,6 +40,8 @@ def reset(self): def calculateRolloff(self, location1, location2): from NanoVNASaver.NanoVNASaver import NanoVNASaver + if location1 == location2: + return 0, 0 frequency1 = self.app.data21[location1].freq frequency2 = self.app.data21[location2].freq gain1 = NanoVNASaver.gain(self.app.data21[location1]) @@ -204,7 +206,7 @@ def runAnalysis(self): # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.sixty_db_label.setText("Not calculated") - if ten_db_location > 0 and twenty_db_location > 0: + if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) self.db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") self.db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") @@ -365,7 +367,7 @@ def runAnalysis(self): # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.sixty_db_label.setText("Not calculated") - if ten_db_location > 0 and twenty_db_location > 0: + if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) self.db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") self.db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") @@ -617,7 +619,7 @@ def runAnalysis(self): # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.lower_sixty_db_label.setText("Not calculated") - if ten_db_location > 0 and twenty_db_location > 0: + if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) self.lower_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") self.lower_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") @@ -679,7 +681,7 @@ def runAnalysis(self): # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.upper_sixty_db_label.setText("Not calculated") - if ten_db_location > 0 and twenty_db_location > 0: + if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) self.upper_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") self.upper_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") @@ -890,7 +892,7 @@ def runAnalysis(self): # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.lower_sixty_db_label.setText("Not calculated") - if ten_db_location > 0 and twenty_db_location > 0: + if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) self.lower_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") self.lower_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") @@ -952,7 +954,7 @@ def runAnalysis(self): # self.sixty_db_label.setText(NanoVNASaver.formatFrequency(sixty_db_cutoff_frequency) + " (derived)") self.upper_sixty_db_label.setText("Not calculated") - if ten_db_location > 0 and twenty_db_location > 0: + if ten_db_location > 0 and twenty_db_location > 0 and ten_db_location != twenty_db_location: octave_attenuation, decade_attenuation = self.calculateRolloff(ten_db_location, twenty_db_location) self.upper_db_per_octave_label.setText(str(round(octave_attenuation, 3)) + " dB / octave") self.upper_db_per_decade_label.setText(str(round(decade_attenuation, 3)) + " dB / decade") diff --git a/NanoVNASaver/NanoVNASaver.py b/NanoVNASaver/NanoVNASaver.py index e4ad441e..76c49a96 100644 --- a/NanoVNASaver/NanoVNASaver.py +++ b/NanoVNASaver/NanoVNASaver.py @@ -1529,7 +1529,7 @@ def askAboutUpdates(self): else: self.app.settings.setValue("CheckForUpdates", "Ask") - def findUpdates(self, automatic = False): + def findUpdates(self, automatic=False): from urllib import request, error import json update_url = "http://mihtjel.dk/nanovna-saver/latest.json" @@ -1562,7 +1562,7 @@ def findUpdates(self, automatic = False): self.updateLabel.setOpenExternalLinks(True) else: # Probably don't show a message box, just update the screen? - # Maybe consider showing it if the user has automatic updates turned off. + # Maybe consider showing it if not an automatic update. # # QtWidgets.QMessageBox.information(self, "No updates available", "There are no new updates available.") # From ffb345103d963c41edbe13c50fb1c839ff3c66e4 Mon Sep 17 00:00:00 2001 From: "Rune B. Broberg" Date: Fri, 11 Oct 2019 17:51:41 +0200 Subject: [PATCH 21/21] 0.1.2 release --- NanoVNASaver/about.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NanoVNASaver/about.py b/NanoVNASaver/about.py index 54f39be7..c7da89dd 100644 --- a/NanoVNASaver/about.py +++ b/NanoVNASaver/about.py @@ -14,5 +14,5 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -version = '0.1.2alpha' -debug = True +version = '0.1.2' +debug = False