Merge branch 'upstream_Development' into feature/refactor_marker

pull/116/head
Holger Mueller 2019-12-13 08:30:10 +01:00
commit 5c45f261cb
8 zmienionych plików z 401 dodań i 103 usunięć

Wyświetl plik

@ -48,6 +48,7 @@ class Chart(QtWidgets.QWidget):
bands = None
draggedMarker: Marker = None
name = ""
sweepTitle = ""
drawLines = False
minChartHeight = 200
minChartWidth = 200
@ -143,6 +144,10 @@ class Chart(QtWidgets.QWidget):
self.markerSize = size
self.update()
def setSweepTitle(self, title):
self.sweepTitle = title
self.update()
def getActiveMarker(self) -> Marker:
if self.draggedMarker is not None:
return self.draggedMarker
@ -309,6 +314,15 @@ class Chart(QtWidgets.QWidget):
number_y = y - self.markerSize - 3
qp.drawText(number_x, number_y, str(number))
def drawTitle(self, qp: QtGui.QPainter, position: QtCore.QPoint = None):
if self.sweepTitle != "":
qp.setPen(self.textColor)
if position is None:
qf = QtGui.QFontMetricsF(self.font())
width = qf.boundingRect(self.sweepTitle).width()
position = QtCore.QPointF(self.width()/2 - width/2, 15)
qp.drawText(position, self.sweepTitle)
class FrequencyChart(Chart):
fstart = 0
@ -691,6 +705,16 @@ class FrequencyChart(Chart):
qp.drawRect(rect)
qp.end()
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, self.topMargin - 5,
self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
self.drawTitle(qp)
def drawFrequencyTicks(self, qp):
fspan = self.fstop - self.fstart
qp.setPen(self.textColor)
@ -896,14 +920,6 @@ class PhaseChart(FrequencyChart):
self.unwrap = unwrap
self.update()
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight,
self.leftMargin + self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
@ -1055,15 +1071,6 @@ class VSWRChart(FrequencyChart):
new_chart.logarithmicY = self.logarithmicY
return new_chart
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, self.topMargin - 5,
self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
@ -1240,6 +1247,8 @@ class PolarChart(SquareChart):
centerX - int(self.chartHeight / 2 * math.sin(math.pi / 4)),
centerY + int(self.chartHeight / 2 * math.sin(math.pi / 4)))
self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
@ -1388,6 +1397,8 @@ class SmithChart(SquareChart):
qp.drawArc(centerX - self.chartWidth*2, centerY,
self.chartWidth*5, -self.chartHeight*5, int(-93.85 * 16), int(-18.85 * 16)) # Im(Z) = 0.2
self.drawTitle(qp)
qp.setPen(self.swrColor)
for swr in self.swrMarkers:
if swr <= 1:
@ -1515,9 +1526,11 @@ class LogMagChart(FrequencyChart):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name + " (dB)")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin, self.topMargin - 5,
self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
@ -1714,7 +1727,8 @@ class SParameterChart(FrequencyChart):
qp.drawText(10, 15, "Real")
qp.drawText(self.leftMargin + self.chartWidth - 15, 15, "Imag")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin, self.topMargin - 5,
self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
@ -1890,7 +1904,8 @@ class CombinedLogMagChart(FrequencyChart):
qp.drawText(10, 15, "S11")
qp.drawText(self.leftMargin + self.chartWidth - 8, 15, "S21")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin, self.topMargin - 5,
self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
@ -2129,12 +2144,7 @@ class QualityFactorChart(FrequencyChart):
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
super().drawChart(qp)
# Make up some sensible scaling here
if self.fixedValues:
@ -2315,6 +2325,9 @@ class TDRChart(Chart):
self.action_popout.triggered.connect(lambda: self.popoutRequested.emit(self))
self.menu.addAction(self.action_popout)
self.chartWidth = self.width() - self.leftMargin - self.rightMargin
self.chartHeight = self.height() - self.bottomMargin - self.topMargin
def contextMenuEvent(self, event):
self.action_set_fixed_start.setText("Start (" + str(self.minDisplayLength) + ")")
self.action_set_fixed_stop.setText("Stop (" + str(self.maxDisplayLength) + ")")
@ -2405,6 +2418,27 @@ class TDRChart(Chart):
if a0.buttons() == QtCore.Qt.RightButton:
a0.ignore()
return
if a0.buttons() == QtCore.Qt.MiddleButton:
# Drag the display
a0.accept()
if self.moveStartX != -1 and self.moveStartY != -1:
dx = self.moveStartX - a0.x()
dy = self.moveStartY - a0.y()
self.zoomTo(self.leftMargin + dx, self.topMargin + dy,
self.leftMargin + self.chartWidth + dx, self.topMargin + self.chartHeight + dy)
self.moveStartX = a0.x()
self.moveStartY = a0.y()
return
if a0.modifiers() == QtCore.Qt.ControlModifier:
# Dragging a box
if not self.draggedBox:
self.draggedBoxStart = (a0.x(), a0.y())
self.draggedBoxCurrent = (a0.x(), a0.y())
self.update()
a0.accept()
return
x = a0.x()
absx = x - self.leftMargin
if absx < 0 or absx > self.width() - self.rightMargin:
@ -2440,6 +2474,8 @@ class TDRChart(Chart):
ticks = math.floor((self.width() - self.leftMargin)/100) # Number of ticks does not include the origin
self.drawTitle(qp)
if len(self.tdrWindow.td) > 0:
if self.fixedSpan:
max_index = np.searchsorted(self.tdrWindow.distance_axis, self.maxDisplayLength * 2)
@ -2529,8 +2565,137 @@ class TDRChart(Chart):
qp.drawText(marker_point.x() - 10, marker_point.y() - 5,
str(round(self.tdrWindow.distance_axis[self.markerLocation] / 2, 2)) + "m")
if self.draggedBox and self.draggedBoxCurrent[0] != -1:
dashed_pen = QtGui.QPen(self.foregroundColor, 1, QtCore.Qt.DashLine)
qp.setPen(dashed_pen)
top_left = QtCore.QPoint(self.draggedBoxStart[0], self.draggedBoxStart[1])
bottom_right = QtCore.QPoint(self.draggedBoxCurrent[0], self.draggedBoxCurrent[1])
rect = QtCore.QRect(top_left, bottom_right)
qp.drawRect(rect)
qp.end()
def valueAtPosition(self, y):
if len(self.tdrWindow.td) > 0:
height = self.height() - self.topMargin - self.bottomMargin
absy = (self.height() - y) - self.bottomMargin
if self.fixedValues:
min_impedance = self.minImpedance
max_impedance = self.maxImpedance
else:
min_impedance = max(0, np.min(self.tdrWindow.step_response_Z) / 1.05)
max_impedance = min(1000, np.max(self.tdrWindow.step_response_Z) * 1.05)
y_step = (max_impedance - min_impedance) / height
return y_step * absy + min_impedance
else:
return 0
def lengthAtPosition(self, x, limit=True):
if len(self.tdrWindow.td) > 0:
width = self.width() - self.leftMargin - self.rightMargin
absx = x - self.leftMargin
if self.fixedSpan:
max_length = self.maxDisplayLength
min_length = self.minDisplayLength
x_step = (max_length - min_length) / width
else:
min_length = 0
max_length = self.tdrWindow.distance_axis[math.ceil(len(self.tdrWindow.distance_axis) / 2)]/2
x_step = max_length / width
if limit and absx < 0:
return min_length
if limit and absx > width:
return max_length
return absx * x_step + min_length
else:
return 0
def zoomTo(self, x1, y1, x2, y2):
logger.debug("Zoom to (x,y) by (x,y): (%d, %d) by (%d, %d)", x1, y1, x2, y2)
val1 = self.valueAtPosition(y1)
val2 = self.valueAtPosition(y2)
if val1 != val2:
self.minImpedance = round(min(val1, val2), 3)
self.maxImpedance = round(max(val1, val2), 3)
self.setFixedValues(True)
len1 = max(0, self.lengthAtPosition(x1, limit=False))
len2 = max(0, self.lengthAtPosition(x2, limit=False))
if len1 >= 0 and len2 >= 0 and len1 != len2:
self.minDisplayLength = min(len1, len2)
self.maxDisplayLength = max(len1, len2)
self.setFixedSpan(True)
self.update()
def wheelEvent(self, a0: QtGui.QWheelEvent) -> None:
if len(self.tdrWindow.td) == 0:
a0.ignore()
return
chart_height = self.chartHeight
chart_width = self.chartWidth
do_zoom_x = do_zoom_y = True
if a0.modifiers() == QtCore.Qt.ShiftModifier:
do_zoom_x = False
if a0.modifiers() == QtCore.Qt.ControlModifier:
do_zoom_y = False
if a0.angleDelta().y() > 0:
# Zoom in
a0.accept()
# Center of zoom = a0.x(), a0.y()
# We zoom in by 1/10 of the width/height.
rate = a0.angleDelta().y() / 120
if do_zoom_x:
zoomx = rate * chart_width / 10
else:
zoomx = 0
if do_zoom_y:
zoomy = rate * chart_height / 10
else:
zoomy = 0
absx = max(0, a0.x() - self.leftMargin)
absy = max(0, a0.y() - self.topMargin)
ratiox = absx/chart_width
ratioy = absy/chart_height
# TODO: Change zoom to center on the mouse if possible, or extend box to the side that has room if not.
p1x = int(self.leftMargin + ratiox * zoomx)
p1y = int(self.topMargin + ratioy * zoomy)
p2x = int(self.leftMargin + chart_width - (1 - ratiox) * zoomx)
p2y = int(self.topMargin + chart_height - (1 - ratioy) * zoomy)
self.zoomTo(p1x, p1y, p2x, p2y)
elif a0.angleDelta().y() < 0:
# Zoom out
a0.accept()
# Center of zoom = a0.x(), a0.y()
# We zoom out by 1/9 of the width/height, to match zoom in.
rate = -a0.angleDelta().y() / 120
if do_zoom_x:
zoomx = rate * chart_width / 9
else:
zoomx = 0
if do_zoom_y:
zoomy = rate * chart_height / 9
else:
zoomy = 0
absx = max(0, a0.x() - self.leftMargin)
absy = max(0, a0.y() - self.topMargin)
ratiox = absx/chart_width
ratioy = absy/chart_height
p1x = int(self.leftMargin - ratiox * zoomx)
p1y = int(self.topMargin - ratioy * zoomy)
p2x = int(self.leftMargin + chart_width + (1 - ratiox) * zoomx)
p2y = int(self.topMargin + chart_height + (1 - ratioy) * zoomy)
self.zoomTo(p1x, p1y, p2x, p2y)
else:
a0.ignore()
def resizeEvent(self, a0: QtGui.QResizeEvent) -> None:
super().resizeEvent(a0)
self.chartWidth = self.width() - self.leftMargin - self.rightMargin
self.chartHeight = self.height() - self.bottomMargin - self.topMargin
class RealImaginaryChart(FrequencyChart):
def __init__(self, name=""):
@ -2619,6 +2784,7 @@ class RealImaginaryChart(FrequencyChart):
qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin + self.chartWidth + 5, self.topMargin + self.chartHeight)
self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
@ -3012,14 +3178,6 @@ class MagnitudeChart(FrequencyChart):
self.setPalette(pal)
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
@ -3158,14 +3316,6 @@ class MagnitudeZChart(FrequencyChart):
self.setPalette(pal)
self.setAutoFillBackground(True)
def drawChart(self, qp: QtGui.QPainter):
qp.setPen(QtGui.QPen(self.textColor))
qp.drawText(3, 15, self.name)
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
return
@ -3329,9 +3479,11 @@ class PermeabilityChart(FrequencyChart):
qp.drawText(10, 15, "R")
qp.drawText(self.leftMargin + self.chartWidth + 10, 15, "X")
qp.setPen(QtGui.QPen(self.foregroundColor))
qp.drawLine(self.leftMargin, self.topMargin - 5, self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin, self.topMargin - 5,
self.leftMargin, self.topMargin + self.chartHeight + 5)
qp.drawLine(self.leftMargin-5, self.topMargin + self.chartHeight,
self.leftMargin + self.chartWidth + 5, self.topMargin + self.chartHeight)
self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
@ -3708,6 +3860,7 @@ class GroupDelayChart(FrequencyChart):
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
@ -3776,8 +3929,56 @@ class GroupDelayChart(FrequencyChart):
self.drawFrequencyTicks(qp)
self.drawData(qp, self.data, self.sweepColor)
self.drawData(qp, self.reference, self.referenceColor)
color = self.sweepColor
pen = QtGui.QPen(color)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(color)
line_pen.setWidth(self.lineThickness)
qp.setPen(pen)
for i in range(len(self.data)):
x = self.getXPosition(self.data[i])
y = self.getYPositionFromDelay(self.groupDelay[i])
if self.isPlotable(x, y):
qp.drawPoint(int(x), int(y))
if self.drawLines and i > 0:
prevx = self.getXPosition(self.data[i - 1])
prevy = self.getYPositionFromDelay(self.groupDelay[i - 1])
qp.setPen(line_pen)
if self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
qp.drawLine(x, y, prevx, prevy)
elif self.isPlotable(x, y) and not self.isPlotable(prevx, prevy):
new_x, new_y = self.getPlotable(x, y, prevx, prevy)
qp.drawLine(x, y, new_x, new_y)
elif not self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
new_x, new_y = self.getPlotable(prevx, prevy, x, y)
qp.drawLine(prevx, prevy, new_x, new_y)
qp.setPen(pen)
color = self.referenceColor
pen = QtGui.QPen(color)
pen.setWidth(self.pointSize)
line_pen = QtGui.QPen(color)
line_pen.setWidth(self.lineThickness)
qp.setPen(pen)
for i in range(len(self.reference)):
x = self.getXPosition(self.reference[i])
y = self.getYPositionFromDelay(self.groupDelayReference[i])
if self.isPlotable(x, y):
qp.drawPoint(int(x), int(y))
if self.drawLines and i > 0:
prevx = self.getXPosition(self.reference[i - 1])
prevy = self.getYPositionFromDelay(self.groupDelayReference[i - 1])
qp.setPen(line_pen)
if self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
qp.drawLine(x, y, prevx, prevy)
elif self.isPlotable(x, y) and not self.isPlotable(prevx, prevy):
new_x, new_y = self.getPlotable(x, y, prevx, prevy)
qp.drawLine(x, y, new_x, new_y)
elif not self.isPlotable(x, y) and self.isPlotable(prevx, prevy):
new_x, new_y = self.getPlotable(prevx, prevy, x, y)
qp.drawLine(prevx, prevy, new_x, new_y)
qp.setPen(pen)
self.drawMarkers(qp)
def getYPosition(self, d: Datapoint) -> int:
@ -3788,6 +3989,9 @@ class GroupDelayChart(FrequencyChart):
delay = self.groupDelayReference[self.reference.index(d)]
else:
delay = 0
return self.getYPositionFromDelay(delay)
def getYPositionFromDelay(self, delay: float):
return self.topMargin + round((self.maxDelay - delay) / self.span * self.chartHeight)
def valueAtPosition(self, y) -> List[float]:
@ -3825,6 +4029,7 @@ class CapacitanceChart(FrequencyChart):
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:
@ -3951,6 +4156,7 @@ class InductanceChart(FrequencyChart):
qp.drawLine(self.leftMargin, 20, self.leftMargin, self.topMargin+self.chartHeight+5)
qp.drawLine(self.leftMargin-5, self.topMargin+self.chartHeight,
self.leftMargin+self.chartWidth, self.topMargin + self.chartHeight)
self.drawTitle(qp)
def drawValues(self, qp: QtGui.QPainter):
if len(self.data) == 0 and len(self.reference) == 0:

Wyświetl plik

@ -57,6 +57,18 @@ class VNA:
logger.warning("Did not recognize NanoVNA type from firmware.")
return NanoVNA(app, serial_port)
def readFeatures(self) -> List[str]:
features = []
raw_help = self.readFromCommand("help")
logger.debug("Help command output:")
logger.debug(raw_help)
# Detect features from the help command
if "capture" in raw_help:
features.append("Screenshots")
return features
def readFrequencies(self) -> List[str]:
pass
@ -113,6 +125,29 @@ class VNA:
logger.error("Unable to acquire serial lock to read firmware.")
return ""
def readFromCommand(self, command) -> str:
if self.app.serialLock.acquire():
result = ""
try:
data = "a"
while data != "":
data = self.serial.readline().decode('ascii')
self.serial.write((command + "\r").encode('ascii'))
result = ""
data = ""
sleep(0.01)
while data != "ch> ":
data = self.serial.readline().decode('ascii')
result += data
except serial.SerialException as exc:
logger.exception("Exception while reading " + command + ": %s", exc)
finally:
self.app.serialLock.release()
return result
else:
logger.error("Unable to acquire serial lock to read " + command + ".")
return ""
def readValues(self, value) -> List[str]:
logger.debug("VNA reading %s", value)
if self.app.serialLock.acquire():
@ -212,6 +247,7 @@ class NanoVNA(VNA):
logger.debug("Older than 0.2.0, using old sweep command.")
self.features.append("Original sweep method")
self.useScan = False
self.features.extend(self.readFeatures())
def isValid(self):
return True

Wyświetl plik

@ -29,19 +29,19 @@ FMT_FREQ_INPUT = SITools.Format(max_nr_digits=10, allow_strip=True,
FMT_Q_FACTOR = SITools.Format(max_nr_digits=4, assume_infinity=False,
min_offset=0, max_offset=0, allow_strip=True)
FMT_GROUP_DELAY = SITools.Format(max_nr_digits=5)
FMT_GROUP_DELAY = SITools.Format(max_nr_digits=5, space_str=" ")
FMT_REACT = SITools.Format(max_nr_digits=5, space_str=" ", allow_strip=True)
COLOR_DEFAULT = QtGui.QColor()
def format_frequency(freq: float, fmt=FMT_FREQ) -> str:
return str(SITools.Value(freq, "Hz", fmt))
def formatFrequency(freq: float) -> str:
return str(SITools.Value(freq, "Hz", FMT_FREQ))
def format_gain(val: float, invert: bool = False) -> str:
if invert:
val = -val
return f"{val:.3f}dB"
return f"{val:.3f} dB"
def format_q_factor(val: float) -> str:
@ -60,7 +60,7 @@ def format_resistance(val: float) -> str:
return str(SITools.Value(val, "\N{OHM SIGN}", FMT_REACT))
def format_capacity(val: float, allow_negative: bool = True) -> str:
def format_capacitance(val: float, allow_negative: bool = True) -> str:
if not allow_negative and val < 0:
return "- pF"
return str(SITools.Value(val, "F", FMT_REACT))
@ -82,40 +82,23 @@ def format_phase(val: float) -> str:
def format_complex_imp(z: complex) -> str:
if z.real > 0:
s = f"{z.real:.4g}"
if z.real >= 1000:
s = f"{z.real/1000:.3g}k"
else:
s = f"{z.real:.4g}"
else:
s = "- "
if z.imag < 0:
s += "-j"
s += " -j"
else:
s += "+j"
return s + f"{abs(z.imag):.4g}\N{OHM SIGN}"
class FrequencyInput(QtWidgets.QLineEdit):
def __init__(self, text=""):
super().__init__(text)
self.nextFrequency = -1
self.previousFrequency = -1
def setText(self, text: str) -> None:
super().setText(format_frequency(text, FMT_FREQ_INPUT))
def keyPressEvent(self, a0: QtGui.QKeyEvent) -> None:
if a0.type() == QtCore.QEvent.KeyPress:
if a0.key() == QtCore.Qt.Key_Up and self.nextFrequency != -1:
a0.accept()
self.setText(self.nextFrequency)
self.textEdited.emit(self.text())
return
if a0.key() == QtCore.Qt.Key_Down and \
self.previousFrequency != -1:
a0.accept()
self.setText(self.previousFrequency)
self.textEdited.emit(self.text())
return
super().keyPressEvent(a0)
s += " +j"
if abs(z.imag) >= 1000:
s += f"{abs(z.imag)/1000:.3g}k"
elif abs(z.imag) < 0.1:
s += f"{abs(z.imag)*1000:.3g}m"
else:
s += f"{abs(z.imag):.3g}"
return s + " \N{OHM SIGN}"
class Marker(QtCore.QObject):
@ -131,6 +114,25 @@ class Marker(QtCore.QObject):
fieldSelection = []
class FrequencyInput(QtWidgets.QLineEdit):
nextFrequency = -1
previousFrequency = -1
def keyPressEvent(self, a0: QtGui.QKeyEvent) -> None:
if a0.type() == QtCore.QEvent.KeyPress:
if a0.key() == QtCore.Qt.Key_Up and self.nextFrequency != -1:
a0.accept()
self.setText(str(self.nextFrequency))
self.textEdited.emit(self.text())
return
if a0.key() == QtCore.Qt.Key_Down and \
self.previousFrequency != -1:
a0.accept()
self.setText(str(self.previousFrequency))
self.textEdited.emit(self.text())
return
super().keyPressEvent(a0)
def __init__(self, name, initialColor, frequency=""):
super().__init__()
self.name = name
@ -400,8 +402,6 @@ class Marker(QtCore.QObject):
if self.location == -1:
return
s11 = s11data[self.location]
if s21data:
s21 = s21data[self.location]
imp = s11.impedance()
cap_str = format_capacity(
@ -454,6 +454,8 @@ class Marker(QtCore.QObject):
if len(s21data) != len(s11data):
return
s21 = s21data[self.location]
self.s21_phase_label.setText(format_phase(s21.phase))
self.gain_label.setText(format_gain(s21.gain))
# TODO: figure out if calculation is right (S11 no division by 2)

Wyświetl plik

@ -59,6 +59,8 @@ class NanoVNASaver(QtWidgets.QWidget):
dataAvailable = QtCore.pyqtSignal()
scaleFactor = 1
sweepTitle = ""
def __init__(self):
super().__init__()
if getattr(sys, 'frozen', False):
@ -654,6 +656,7 @@ class NanoVNASaver(QtWidgets.QWidget):
sleep(0.05)
self.vna = VNA.getVNA(self, self.serial)
self.vna.validateInput = self.settings.value("SerialInputValidation", True, bool)
self.worker.setVNA(self.vna)
logger.info(self.vna.readFirmware())
@ -727,6 +730,8 @@ class NanoVNASaver(QtWidgets.QWidget):
self.dataLock.release()
if source is not None:
self.sweepSource = source
elif self.sweepTitle != "":
self.sweepSource = self.sweepTitle + " " + strftime("%Y-%m-%d %H:%M:%S", localtime())
else:
self.sweepSource = strftime("%Y-%m-%d %H:%M:%S", localtime())
@ -964,6 +969,7 @@ class NanoVNASaver(QtWidgets.QWidget):
def showSweepError(self):
self.showError(self.worker.error_message)
self.serial.flushInput() # Remove any left-over data
self.sweepFinished()
def popoutChart(self, chart: Chart):
@ -1014,6 +1020,11 @@ class NanoVNASaver(QtWidgets.QWidget):
m.getGroupBox().setFont(font)
m.setScale(self.scaleFactor)
def setSweepTitle(self, title):
self.sweepTitle = title
for c in self.subscribing_charts:
c.setSweepTitle(title)
class DisplaySettingsWindow(QtWidgets.QWidget):
def __init__(self, app: NanoVNASaver):
@ -2023,6 +2034,20 @@ class SweepSettingsWindow(QtWidgets.QWidget):
layout = QtWidgets.QVBoxLayout()
self.setLayout(layout)
title_box = QtWidgets.QGroupBox("Sweep name")
title_layout = QtWidgets.QFormLayout(title_box)
self.sweep_title_input = QtWidgets.QLineEdit()
title_layout.addRow("Sweep name", self.sweep_title_input)
title_button_layout = QtWidgets.QHBoxLayout()
btn_set_sweep_title = QtWidgets.QPushButton("Set")
btn_set_sweep_title.clicked.connect(lambda: self.app.setSweepTitle(self.sweep_title_input.text()))
btn_reset_sweep_title = QtWidgets.QPushButton("Reset")
btn_reset_sweep_title.clicked.connect(lambda: self.app.setSweepTitle(""))
title_button_layout.addWidget(btn_set_sweep_title)
title_button_layout.addWidget(btn_reset_sweep_title)
title_layout.addRow(title_button_layout)
layout.addWidget(title_box)
settings_box = QtWidgets.QGroupBox("Settings")
settings_layout = QtWidgets.QFormLayout(settings_box)
@ -2594,7 +2619,8 @@ class DeviceSettingsWindow(QtWidgets.QWidget):
settings_layout = QtWidgets.QFormLayout(settings_box)
self.chkValidateInputData = QtWidgets.QCheckBox("Validate received data")
self.chkValidateInputData.setChecked(True)
validate_input = self.app.settings.value("SerialInputValidation", True, bool)
self.chkValidateInputData.setChecked(validate_input)
self.chkValidateInputData.stateChanged.connect(self.updateValidation)
settings_layout.addRow("Validation", self.chkValidateInputData)
@ -2626,16 +2652,24 @@ class DeviceSettingsWindow(QtWidgets.QWidget):
self.featureList.clear()
self.featureList.addItem(self.app.vna.name + " v" + str(self.app.vna.version))
for item in self.app.vna.getFeatures():
features = self.app.vna.getFeatures()
for item in features:
self.featureList.addItem(item)
if "Screenshots" in features:
self.btnCaptureScreenshot.setDisabled(False)
else:
self.btnCaptureScreenshot.setDisabled(True)
else:
self.statusLabel.setText("Not connected.")
self.calibrationStatusLabel.setText("Not connected.")
self.featureList.clear()
self.featureList.addItem("Not connected.")
self.btnCaptureScreenshot.setDisabled(True)
def updateValidation(self, validate_data: bool):
self.app.vna.validateInput = validate_data
self.app.settings.setValue("SerialInputValidation", validate_data)
def captureScreenshot(self):
if not self.app.worker.running:

Wyświetl plik

@ -41,8 +41,8 @@ def serial_to_parallel(z: complex) -> complex:
z_sq_sum / z.imag)
def impedance_to_capacity(z: complex, freq: float) -> float:
"""Calculate capacitve equivalent for reactance"""
def impedance_to_capacitance(z: complex, freq: float) -> float:
"""Calculate capacitive equivalent for reactance"""
if freq == 0:
return -math.inf
if z.imag == 0:
@ -77,6 +77,13 @@ def gamma_to_impedance(gamma: complex, ref_impedance: float = 50) -> complex:
return ((-gamma - 1) / (gamma - 1)) * ref_impedance
def parseFrequency(freq: str) -> int:
try:
return int(Value(freq, "Hz", FMT_PARSE))
except (ValueError, IndexError):
return -1
class Datapoint(NamedTuple):
freq: int
re: float
@ -116,7 +123,7 @@ class Datapoint(NamedTuple):
return abs(imp.imag / imp.real)
def capacitiveEquivalent(self, ref_impedance: float = 50) -> float:
return impedance_to_capacity(
return impedance_to_capacitance(
self.impedance(ref_impedance), self.freq)
def inductiveEquivalent(self, ref_impedance: float = 50) -> float:
@ -141,6 +148,7 @@ def groupDelay(data: List[Datapoint], index: int) -> float:
class RFTools:
# TODO: Remove this class when unused
@staticmethod
def formatFrequency(freq: Number) -> str:
return str(Value(freq, "Hz", FMT_FREQ))
@ -155,7 +163,4 @@ class RFTools:
@staticmethod
def parseFrequency(freq: str) -> int:
try:
return int(Value(freq, "Hz", FMT_PARSE))
except (ValueError, IndexError):
return -1
return parseFrequency(freq)

Wyświetl plik

@ -3,7 +3,7 @@
# Copyright (C) 2019. Rune B. Broberg
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published bynanovna
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#

Wyświetl plik

@ -142,7 +142,12 @@ class SweepWorker(QtCore.QRunnable):
self.error_message = str(e)
self.stopped = True
self.running = False
self.signals.fatalSweepError.emit()
self.signals.sweepError.emit()
except NanoVNASerialException as e:
self.error_message = str(e)
self.stopped = True
self.running = False
self.signals.sweepFatalError.emit()
while self.continuousSweep and not self.stopped:
logger.debug("Continuous sweeping")
@ -160,7 +165,12 @@ class SweepWorker(QtCore.QRunnable):
self.error_message = str(e)
self.stopped = True
self.running = False
self.signals.fatalSweepError.emit()
self.signals.sweepError.emit()
except NanoVNASerialException as e:
self.error_message = str(e)
self.stopped = True
self.running = False
self.signals.sweepFatalError.emit()
# Reset the device to show the full range
logger.debug("Resetting NanoVNA sweep to full range: %d to %d",
@ -332,16 +342,16 @@ class SweepWorker(QtCore.QRunnable):
tmpdata = self.vna.readValues(data)
if not tmpdata:
logger.warning("Read no values")
raise NanoVNAValueException("Failed reading data: Returned no values.")
raise NanoVNASerialException("Failed reading data: Returned no values.")
logger.debug("Read %d values", len(tmpdata))
for d in tmpdata:
a, b = d.split(" ")
try:
if self.vna.validateInput and float(a) < -9.5 or float(a) > 9.5:
if self.vna.validateInput and (float(a) < -9.5 or float(a) > 9.5):
logger.warning("Got a non-float data value: %s (%s)", d, a)
logger.debug("Re-reading %s", data)
done = False
elif self.vna.validateInput and float(b) < -9.5 or float(b) > 9.5:
elif self.vna.validateInput and (float(b) < -9.5 or float(b) > 9.5):
logger.warning("Got a non-float data value: %s (%s)", d, b)
logger.debug("Re-reading %s", data)
done = False
@ -359,7 +369,8 @@ class SweepWorker(QtCore.QRunnable):
if count >= 20:
logger.critical("Tried and failed to read %s %d times. Giving up.", data, count)
raise NanoVNAValueException("Failed reading " + str(data) + " " + str(count) + " times.\n" +
"Data outside expected valid ranges, or in an unexpected format.")
"Data outside expected valid ranges, or in an unexpected format.\n\n" +
"You can disable data validation on the device settings screen.")
return returndata
def readFreq(self):
@ -374,7 +385,7 @@ class SweepWorker(QtCore.QRunnable):
tmpfreq = self.vna.readFrequencies()
if not tmpfreq:
logger.warning("Read no frequencies")
raise NanoVNAValueException("Failed reading frequencies: Returned no values.")
raise NanoVNASerialException("Failed reading frequencies: Returned no values.")
for f in tmpfreq:
if not f.isdigit():
logger.warning("Got a non-digit frequency: %s", f)
@ -407,3 +418,7 @@ class SweepWorker(QtCore.QRunnable):
class NanoVNAValueException(Exception):
pass
class NanoVNASerialException(Exception):
pass

Wyświetl plik

@ -21,7 +21,7 @@ from NanoVNASaver.RFTools import Datapoint, \
norm_to_impedance, impedance_to_norm, \
reflection_coefficient, gamma_to_impedance, clamp_value, \
parallel_to_serial, serial_to_parallel, \
impedance_to_capacity, impedance_to_inductance
impedance_to_capacitance, impedance_to_inductance
import math
@ -83,10 +83,10 @@ class TestRFTools(unittest.TestCase):
complex(52, 260))
def test_impedance_to_capacity(self):
self.assertEqual(impedance_to_capacity(0, 0), -math.inf)
self.assertEqual(impedance_to_capacity(0, 10), math.inf)
self.assertEqual(impedance_to_capacitance(0, 0), -math.inf)
self.assertEqual(impedance_to_capacitance(0, 10), math.inf)
self.assertAlmostEqual(
impedance_to_capacity(complex(50, 159.1549), 100000),
impedance_to_capacitance(complex(50, 159.1549), 100000),
1e-8)
def test_impedance_to_inductance(self):