- First version of Resistance/Reactance chart

- Added selection of secondary colours
- Exception handling for serial port had a crash error (Fixes #20)
pull/23/head
Rune B. Broberg 2019-09-22 13:42:05 +02:00
rodzic c0ed1ecee6
commit 44fd87c7d6
2 zmienionych plików z 316 dodań i 36 usunięć

Wyświetl plik

@ -17,15 +17,18 @@ import collections
import math
from typing import List
import numpy as np
import logging
from PyQt5 import QtWidgets, QtGui, QtCore
from .Marker import Marker
logger = logging.getLogger(__name__)
Datapoint = collections.namedtuple('Datapoint', 'freq re im')
class Chart(QtWidgets.QWidget):
sweepColor = QtCore.Qt.darkYellow
secondarySweepColor = QtCore.Qt.darkMagenta
referenceColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.blue)
referenceColor.setAlpha(64)
backgroundColor: QtGui.QColor = QtGui.QColor(QtCore.Qt.white)
@ -48,6 +51,10 @@ class Chart(QtWidgets.QWidget):
self.sweepColor = color
self.update()
def setSecondarySweepColor(self, color : QtGui.QColor):
self.secondarySweepColor = color
self.update()
def setReferenceColor(self, color : QtGui.QColor):
self.referenceColor = color
self.update()
@ -166,9 +173,6 @@ class PhaseChart(Chart):
self.setPalette(pal)
self.setAutoFillBackground(True)
self.marker1Color = QtGui.QColor(255, 0, 20)
self.marker2Color = QtGui.QColor(20, 0, 255)
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
self.chartWidth = a0.size().width()-20-self.leftMargin
self.chartHeight = a0.size().height()-40
@ -320,9 +324,6 @@ class VSWRChart(Chart):
self.setPalette(pal)
self.setAutoFillBackground(True)
self.marker1Color = QtGui.QColor(255, 0, 20)
self.marker2Color = QtGui.QColor(20, 0, 255)
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
self.chartWidth = a0.size().width()-20-self.leftMargin
self.chartHeight = a0.size().height()-40
@ -492,11 +493,6 @@ class PolarChart(SquareChart):
self.setPalette(pal)
self.setAutoFillBackground(True)
self.marker1Color = QtGui.QColor(255, 0, 20)
self.marker2Color = QtGui.QColor(20, 0, 255)
self.sweepColor = QtGui.QColor(220, 200, 30, 128)
self.sweepColor = QtGui.QColor(50, 50, 200, 64)
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
qp = QtGui.QPainter(self)
self.drawChart(qp)
@ -624,11 +620,6 @@ class SmithChart(SquareChart):
self.setPalette(pal)
self.setAutoFillBackground(True)
self.marker1Color = QtGui.QColor(255, 0, 20)
self.marker2Color = QtGui.QColor(20, 0, 255)
self.sweepColor = QtGui.QColor(220, 200, 30, 128)
self.sweepColor = QtGui.QColor(50, 50, 200, 64)
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
qp = QtGui.QPainter(self)
#qp.begin(self) # Apparently not needed?
@ -771,9 +762,6 @@ class LogMagChart(Chart):
self.setPalette(pal)
self.setAutoFillBackground(True)
self.marker1Color = QtGui.QColor(255, 0, 20)
self.marker2Color = QtGui.QColor(20, 0, 255)
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
self.chartWidth = a0.size().width()-20-self.leftMargin
self.chartHeight = a0.size().height()-40
@ -950,9 +938,6 @@ class QualityFactorChart(Chart):
self.setPalette(pal)
self.setAutoFillBackground(True)
self.marker1Color = QtGui.QColor(255, 0, 20)
self.marker2Color = QtGui.QColor(20, 0, 255)
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
self.chartWidth = a0.size().width()-20-self.leftMargin
self.chartHeight = a0.size().height()-40
@ -1110,7 +1095,7 @@ class TDRChart(Chart):
self.leftMargin = 20
self.rightMargin = 20
self.lowerMargin = 35
self.setMinimumSize(400, 400)
self.setMinimumSize(250, 250)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
@ -1155,3 +1140,272 @@ class TDRChart(Chart):
qp.setPen(self.textColor)
qp.drawText(max_point.x() - 10, max_point.y() - 5, str(round(self.tdrWindow.distance_axis[id_max]/2, 2)) + "m")
qp.end()
class RealImaginaryChart(Chart):
def __init__(self, name=""):
super().__init__(name)
self.leftMargin = 35
self.rightMargin = 35
self.chartWidth = 230
self.chartHeight = 250
self.fstart = 0
self.fstop = 0
self.span_real = 0
self.span_imag = 0
self.max_real = 0
self.max_imag = 0
self.setMinimumSize(self.chartWidth + self.leftMargin + self.rightMargin, self.chartHeight + 40)
self.setSizePolicy(QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding))
pal = QtGui.QPalette()
pal.setColor(QtGui.QPalette.Background, self.backgroundColor)
self.setPalette(pal)
self.setAutoFillBackground(True)
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
self.chartWidth = a0.size().width()-self.leftMargin-self.rightMargin
self.chartHeight = a0.size().height()-40
self.update()
def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
qp = QtGui.QPainter(self)
#qp.begin(self) # Apparently not needed?
self.drawChart(qp)
self.drawValues(qp)
qp.end()
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(self.leftMargin + 5, 15, self.name + " (\N{OHM SIGN})")
qp.drawText(10, 15, "R")
qp.drawText(self.leftMargin + self.chartWidth + 10, 15, "X")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, 20+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, 20+self.chartHeight, self.leftMargin+self.chartWidth+5, 20 + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
from NanoVNASaver.NanoVNASaver import NanoVNASaver
if len(self.data) == 0 and len(self.reference) == 0:
return
pen = QtGui.QPen(self.sweepColor)
pen.setWidth(2)
line_pen = QtGui.QPen(self.sweepColor)
line_pen.setWidth(1)
highlighter = QtGui.QPen(QtGui.QColor(20, 0, 255))
highlighter.setWidth(1)
if len(self.data) > 0:
fstart = self.data[0].freq
fstop = self.data[len(self.data)-1].freq
else:
fstart = self.reference[0].freq
fstop = self.reference[len(self.reference) - 1].freq
self.fstart = fstart
self.fstop = fstop
fspan = fstop-fstart
# Find scaling
min_real = 1000
min_imag = 1000
max_real = 0
max_imag = -1000
for d in self.data:
re, im = NanoVNASaver.normalize50(d)
if re > max_real:
max_real = re
if re < min_real:
min_real = re
if im > max_imag:
max_imag = im
if im < min_imag:
min_imag = im
for d in self.reference: # Also check min/max for the reference sweep
if d.freq < fstart or d.freq > fstop:
continue
re, im = NanoVNASaver.normalize50(d)
if re > max_real:
max_real = re
if re < min_real:
min_real = re
if im > max_imag:
max_imag = im
if im < min_imag:
min_imag = im
max_real = math.ceil(max_real)
min_real = max(0, math.floor(min_real)) # Negative real resistance? No.
max_imag = math.ceil(max_imag)
min_imag = math.floor(min_imag)
self.max_real = max_real
self.max_imag = max_imag
span_real = max_real - min_real
self.span_real = span_real
span_imag = max_imag - min_imag
self.span_imag = span_imag
# We want one horizontal tick per 50 pixels, at most
horizontal_ticks = math.floor(self.chartHeight/50)
# TODO: Find a way to always have a line at X=0
for i in range(horizontal_ticks):
y = 30 + round(i * (self.chartHeight-10) / horizontal_ticks)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin - 5, y, self.leftMargin + self.chartWidth + 5, y)
qp.setPen(QtGui.QPen(self.textColor))
re = max_real - round(i * span_real / horizontal_ticks)
im = max_imag - round(i * span_imag / horizontal_ticks)
qp.drawText(3, y + 4, str(re))
qp.drawText(self.leftMargin + self.chartWidth + 8, y + 4, str(im))
qp.drawText(3, self.chartHeight + 20, str(min_real))
qp.drawText(self.leftMargin + self.chartWidth + 8, self.chartHeight + 20, str(min_imag))
qp.drawText(self.leftMargin-20, 20 + self.chartHeight + 15, LogMagChart.shortenFrequency(fstart))
ticks = math.floor(self.chartWidth/100) # Number of ticks does not include the origin
for i in range(ticks):
x = self.leftMargin + round((i+1)*self.chartWidth/ticks)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(x, 20, x, 20+self.chartHeight+5)
qp.setPen(self.textColor)
qp.drawText(x-20, 20+self.chartHeight+15, LogMagChart.shortenFrequency(round(fspan/ticks*(i+1) + fstart)))
primary_pen = pen
secondary_pen = QtGui.QPen(self.secondarySweepColor)
secondary_pen.setWidth(2)
if len(self.data) > 0:
c = QtGui.QColor(self.sweepColor)
c.setAlpha(255)
pen = QtGui.QPen(c)
pen.setWidth(2)
qp.setPen(pen)
qp.drawLine(20, 9, 25, 9)
c = QtGui.QColor(self.secondarySweepColor)
c.setAlpha(255)
pen.setColor(c)
qp.setPen(pen)
qp.drawLine(self.leftMargin + self.chartWidth, 9, self.leftMargin + self.chartWidth + 5, 9)
for i in range(len(self.data)):
re, im = NanoVNASaver.normalize50(self.data[i])
x = self.getXPosition(self.data[i])
y_re = 30 + round((max_real - re) / span_real * (self.chartHeight - 10))
y_im = 30 + round((max_imag - im) / span_imag * (self.chartHeight - 10))
qp.setPen(primary_pen)
if re > 0:
qp.drawPoint(int(x), int(y_re))
qp.setPen(secondary_pen)
qp.drawPoint(int(x), int(y_im))
if self.drawLines and i > 0:
new_re, new_im = NanoVNASaver.normalize50(self.data[i-1])
prev_x = self.getXPosition(self.data[i-1])
prev_y_re = 30 + round((max_real - new_re) / span_real * (self.chartHeight - 10))
prev_y_im = 30 + round((max_imag - new_im) / span_imag * (self.chartHeight - 10))
if re > 0 and new_re > 0:
line_pen.setColor(self.sweepColor)
qp.setPen(line_pen)
qp.drawLine(x, y_re, prev_x, prev_y_re)
line_pen.setColor(self.secondarySweepColor)
qp.setPen(line_pen)
qp.drawLine(x, y_im, prev_x, prev_y_im)
primary_pen.setColor(self.referenceColor)
line_pen.setColor(self.referenceColor)
secondary_pen.setColor(self.referenceColor)
qp.setPen(primary_pen)
if len(self.reference) > 0:
c = QtGui.QColor(self.referenceColor)
c.setAlpha(255)
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(self.leftMargin + self.chartWidth, 14, self.leftMargin + self.chartWidth + 5, 14)
for i in range(len(self.reference)):
if self.reference[i].freq < fstart or self.reference[i].freq > fstop:
continue
re, im = NanoVNASaver.normalize50(self.reference[i])
x = self.getXPosition(self.reference[i])
y_re = 30 + round((max_real - re) / span_real * (self.chartHeight - 10))
y_im = 30 + round((max_imag - im) / span_imag * (self.chartHeight - 10))
qp.setPen(primary_pen)
if re > 0:
qp.drawPoint(int(x), int(y_re))
qp.setPen(secondary_pen)
qp.drawPoint(int(x), int(y_im))
# if self.drawLines and i > 0:
# logmag = self.logMag(self.reference[i-1])
# prevx = self.leftMargin + 1 + round(self.chartWidth*(self.reference[i-1].freq - fstart)/fspan)
# prevy = 30 + round((logmag - minValue) / span * (self.chartHeight - 10))
# qp.setPen(line_pen)
# qp.drawLine(x, y, prevx, prevy)
# qp.setPen(pen)
# Now draw the markers
for m in self.markers:
if m.location != -1:
highlighter.setColor(m.color)
qp.setPen(highlighter)
re, im = NanoVNASaver.normalize50(self.data[m.location])
x = self.getXPosition(self.data[m.location])
y_re = 30 + round((max_real - re) / span_real * (self.chartHeight - 10))
y_im = 30 + round((max_imag - im) / span_imag * (self.chartHeight - 10))
qp.drawLine(int(x), int(y_re) + 3, int(x) - 3, int(y_re) - 3)
qp.drawLine(int(x), int(y_re) + 3, int(x) + 3, int(y_re) - 3)
qp.drawLine(int(x) - 3, int(y_re) - 3, int(x) + 3, int(y_re) - 3)
qp.drawLine(int(x), int(y_im) + 3, int(x) - 3, int(y_im) - 3)
qp.drawLine(int(x), int(y_im) + 3, int(x) + 3, int(y_im) - 3)
qp.drawLine(int(x) - 3, int(y_im) - 3, int(x) + 3, int(y_im) - 3)
def getXPosition(self, d: Datapoint) -> int:
span = self.fstop - self.fstart
return self.leftMargin + 1 + round(self.chartWidth * (d.freq - self.fstart) / span)
def getImYPosition(self, d: Datapoint) -> int:
from NanoVNASaver.NanoVNASaver import NanoVNASaver
re, im = NanoVNASaver.normalize50(d)
return 30 + round((self.max_imag - im) / self.span_imag * (self.chartHeight - 10))
def getReYPosition(self, d: Datapoint) -> int:
from NanoVNASaver.NanoVNASaver import NanoVNASaver
re, im = NanoVNASaver.normalize50(d)
return 30 + round((self.max_real - re) / self.span_real * (self.chartHeight - 10))
def getNearestMarker(self, x, y) -> Marker:
if len(self.data) == 0:
return None
shortest = 10**6
nearest = None
for m in self.markers:
mx, _ = self.getPosition(self.data[m.location])
myr = self.getReYPosition(self.data[m.location])
myi = self.getImYPosition(self.data[m.location])
dx = abs(x - mx)
dy = min(abs(y - myr), abs(y-myi))
distance = math.sqrt(dx**2 + dy**2)
if distance < shortest:
shortest = distance
nearest = m
return nearest
def mouseMoveEvent(self, a0: QtGui.QMouseEvent) -> None:
x = a0.x()
absx = x - self.leftMargin
if absx < 0 or absx > self.chartWidth:
a0.ignore()
return
a0.accept()
if self.fstop - self.fstart > 0:
m = self.getActiveMarker(a0)
span = self.fstop - self.fstart
step = span/self.chartWidth
f = self.fstart + absx * step
m.setFrequency(str(round(f)))
m.frequencyInput.setText(str(round(f)))
return

Wyświetl plik

@ -26,7 +26,8 @@ import serial
from PyQt5 import QtWidgets, QtCore, QtGui
from serial.tools import list_ports
from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagChart, QualityFactorChart, TDRChart
from .Chart import Chart, PhaseChart, VSWRChart, PolarChart, SmithChart, LogMagChart, QualityFactorChart, TDRChart, \
RealImaginaryChart
from .Calibration import CalibrationWindow, Calibration
from .Marker import Marker
from .SweepWorker import SweepWorker
@ -103,12 +104,14 @@ class NanoVNASaver(QtWidgets.QWidget):
self.s21Phase = PhaseChart("S21 Phase")
self.s11VSWR = VSWRChart("S11 VSWR")
self.s11QualityFactor = QualityFactorChart("S11 Quality Factor")
self.s11RealImaginary = RealImaginaryChart("S11 R+jX")
self.s11charts: List[Chart] = []
self.s11charts.append(self.s11SmithChart)
self.s11charts.append(self.s11LogMag)
self.s11charts.append(self.s11Phase)
self.s11charts.append(self.s11VSWR)
self.s11charts.append(self.s11RealImaginary)
self.s11charts.append(self.s11QualityFactor)
self.s21charts: List[Chart] = []
@ -568,7 +571,7 @@ class NanoVNASaver(QtWidgets.QWidget):
self.serial = serial.Serial(port=self.serialPort, baudrate=115200)
self.serial.timeout = 0.05
except serial.SerialException as exc:
logger.error("Tried to open %s and failed: ", self.serialPort, exc)
logger.error("Tried to open %s and failed: %s", self.serialPort, exc)
self.serialLock.release()
return
self.btnSerialToggle.setText("Disconnect")
@ -756,7 +759,7 @@ class NanoVNASaver(QtWidgets.QWidget):
@staticmethod
def vswr(data: Datapoint):
im50, re50 = NanoVNASaver.normalize50(data)
re50, im50 = NanoVNASaver.normalize50(data)
mag = math.sqrt((re50 - 50) * (re50 - 50) + im50 * im50) / math.sqrt((re50 + 50) * (re50 + 50) + im50 * im50)
# mag = math.sqrt(re * re + im * im) # Is this even right?
vswr = (1 + mag) / (1 - mag)
@ -783,7 +786,7 @@ class NanoVNASaver(QtWidgets.QWidget):
@staticmethod
def gain(data: Datapoint):
im50, re50 = NanoVNASaver.normalize50(data)
re50, im50 = NanoVNASaver.normalize50(data)
# Calculate the gain / reflection coefficient
mag = math.sqrt((re50 - 50) * (re50 - 50) + im50 * im50) / math.sqrt(
(re50 + 50) * (re50 + 50) + im50 * im50)
@ -795,7 +798,7 @@ class NanoVNASaver(QtWidgets.QWidget):
im = data.im
re50 = 50 * (1 - re * re - im * im) / (1 + re * re + im * im - 2 * re)
im50 = 50 * (2 * im) / (1 + re * re + im * im - 2 * re)
return im50, re50
return re50, im50
@staticmethod
def admittance(data):
@ -1011,6 +1014,18 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
display_options_layout.addRow("Sweep color", self.btnColorPicker)
self.btnSecondaryColorPicker = QtWidgets.QPushButton("")
self.btnSecondaryColorPicker.setFixedWidth(20)
self.secondarySweepColor = self.app.settings.value("SecondarySweepColor",
defaultValue=QtGui.QColor(20, 160, 140, 128),
type=QtGui.QColor)
self.setSecondarySweepColor(self.secondarySweepColor)
self.btnSecondaryColorPicker.clicked.connect(lambda: self.setSecondarySweepColor(
QtWidgets.QColorDialog.getColor(self.secondarySweepColor,
options=QtWidgets.QColorDialog.ShowAlphaChannel)))
display_options_layout.addRow("Second sweep color", self.btnSecondaryColorPicker)
self.btnReferenceColorPicker = QtWidgets.QPushButton("")
self.btnReferenceColorPicker.setFixedWidth(20)
self.referenceColor = self.app.settings.value("ReferenceColor", defaultValue=QtGui.QColor(0, 0, 255, 32),
@ -1248,6 +1263,17 @@ class DisplaySettingsWindow(QtWidgets.QWidget):
for c in self.app.charts:
c.setSweepColor(color)
def setSecondarySweepColor(self, color: QtGui.QColor):
if color.isValid():
self.secondarySweepColor = color
p = self.btnSecondaryColorPicker.palette()
p.setColor(QtGui.QPalette.ButtonText, color)
self.btnSecondaryColorPicker.setPalette(p)
self.app.settings.setValue("SecondarySweepColor", color)
self.app.settings.sync()
for c in self.app.charts:
c.setSecondarySweepColor(color)
def setReferenceColor(self, color):
if color.isValid():
self.referenceColor = color
@ -1294,17 +1320,17 @@ class TDRWindow(QtWidgets.QWidget):
self.tdr_velocity_dropdown.addItem("Air (Helical spacers) (0.94)", 0.94)
self.tdr_velocity_dropdown.insertSeparator(self.tdr_velocity_dropdown.count())
# Lots of table types added by Larry Goga, AE5CZ
self.tdr_velocity_dropdown.addItem("RG-6/U PE 75ohm (Belden 8215) (0.66)", 0.66)
self.tdr_velocity_dropdown.addItem("RG-6/U Foam 75 ohm (Belden 9290) (0.81)", 0.81)
self.tdr_velocity_dropdown.addItem("RG-8/U PE 50 ohm (Belden 8237) (0.66)", 0.66)
self.tdr_velocity_dropdown.addItem("RG-6/U PE 75\N{OHM SIGN} (Belden 8215) (0.66)", 0.66)
self.tdr_velocity_dropdown.addItem("RG-6/U Foam 75\N{OHM SIGN} (Belden 9290) (0.81)", 0.81)
self.tdr_velocity_dropdown.addItem("RG-8/U PE 50\N{OHM SIGN} (Belden 8237) (0.66)", 0.66)
self.tdr_velocity_dropdown.addItem("RG-8/U Foam (Belden 8214) (0.78)", 0.78)
self.tdr_velocity_dropdown.addItem("RG-8/U (Belden 9913) (0.84)", 0.84)
self.tdr_velocity_dropdown.addItem("RG-8X (Belden 9258) (0.82)", 0.82)
self.tdr_velocity_dropdown.addItem("RG-11/U 75 ohm Foam HDPE (Belden 9292) (0.84)", 0.84)
self.tdr_velocity_dropdown.addItem("RG-58/U 52 ohm PE (Belden 9201) (0.66)", 0.66)
self.tdr_velocity_dropdown.addItem("RG-58A/U 54 ohm Foam (Belden 8219) (0.73)", 0.73)
self.tdr_velocity_dropdown.addItem("RG-59A/U PE 75 ohm (Belden 8241) (0.66)", 0.66)
self.tdr_velocity_dropdown.addItem("RG-59A/U Foam 75 ohm (Belden 8241F) (0.78)", 0.78)
self.tdr_velocity_dropdown.addItem("RG-11/U 75\N{OHM SIGN} Foam HDPE (Belden 9292) (0.84)", 0.84)
self.tdr_velocity_dropdown.addItem("RG-58/U 52\N{OHM SIGN} PE (Belden 9201) (0.66)", 0.66)
self.tdr_velocity_dropdown.addItem("RG-58A/U 54\N{OHM SIGN} Foam (Belden 8219) (0.73)", 0.73)
self.tdr_velocity_dropdown.addItem("RG-59A/U PE 75\N{OHM SIGN} (Belden 8241) (0.66)", 0.66)
self.tdr_velocity_dropdown.addItem("RG-59A/U Foam 75\N{OHM SIGN} (Belden 8241F) (0.78)", 0.78)
self.tdr_velocity_dropdown.addItem("RG-174 PE (Belden 8216)(0.66)", 0.66)
self.tdr_velocity_dropdown.addItem("RG-174 Foam (Belden 7805R) (0.735)", 0.735)
self.tdr_velocity_dropdown.addItem("RG-213/U PE (Belden 8267) (0.66)", 0.66)