Skip to content

Commit

Permalink
add Live View for viewing monitor screen of NanoVNA and TinySA in rea…
Browse files Browse the repository at this point in the history
…l time (#762)

* add nanovna f v3 in hardware
* fix SN number not showing in nanovna F v3 using nanovna-saver
* add hardware settings for NanoVNA F V3 & set data valid points to 101, 11, 51, 201, 301, 501, 801
* fix showing serial number on different VNAs whether they use different uppercase SN commands or lowercase sn commands
* add support for NanoVNA_F_V2 for reaching max datapoints up to 201 and 301 on different firmwares
* add option Hardware in Device Settings for some models with hardware revision such as TinySA and TinySA Ultra
* add Live View for viewing monitor screen of NanoVNA and TinySA in real time
* set interval fresh every 2000ms for not getting more time for receiving screen data via USB from nanovna and tinysa. performance improved a little.

---------

Co-authored-by: Holger Müller <[email protected]>
  • Loading branch information
xros and zarath authored Feb 27, 2025
1 parent e278567 commit 0301103
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 5 deletions.
1 change: 0 additions & 1 deletion src/NanoVNASaver/Hardware/Hardware.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ def get_VNA(iface: Interface) -> VNA:
# serial_port.timeout = TIMEOUT
return NAME2DEVICE[iface.comment](iface)


def get_comment(iface: Interface) -> str:
logger.info("Finding correct VNA type...")
with iface.lock:
Expand Down
39 changes: 38 additions & 1 deletion src/NanoVNASaver/Hardware/NanoVNA_F_V2.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,25 @@ class NanoVNA_F_V2(NanoVNA):
name = "NanoVNA-F_V2"
screenwidth = 800
screenheight = 480

valid_datapoints = (101, 11, 51, 201, 301)
sweep_points_min = 11
sweep_points_max = 301

def __init__(self, iface: Interface):
super().__init__(iface)
self.sweep_max_freq_hz = 3e9
self.version = self.read_firmware_version()
# max datapoints reach up to 301 since version 0.5.0
if self.version >= Version("0.5.0"):
pass
# max datapoints reach up to 201 since version 0.2.0
elif self.version >= Version("0.2.0"):
self.valid_datapoints = (101, 11, 51, 201)
self.sweep_points_max = 201
# max datapoints reach up to 101 before version 0.2.0
else:
self.valid_datapoints = (101, 11, 51)
self.sweep_points_max = 101

def getScreenshot(self) -> QPixmap:
logger.debug("Capturing screenshot...")
Expand All @@ -49,3 +64,25 @@ def getScreenshot(self) -> QPixmap:
except serial.SerialException as exc:
logger.exception("Exception while capturing screenshot: %s", exc)
return QPixmap()

def read_firmware_version(self) -> "Version":
"""For example, command version in NanoVNA_F_V2 and NanoVNA_F_V3 returns as this
0.5.8
"""
result = list(self.exec_command("version"))
logger.debug("firmware version result:\n%s", result[0])
return Version(result[0])

def read_features(self):
super().read_features()
result = " ".join(self.exec_command("help")).split()
if "sn:" or "SN:" in result:
self.features.add("SN")
self.SN = self.getSerialNumber()

def getSerialNumber(self) -> str:
return (
" ".join(list(self.exec_command("SN")))
if "SN:" in " ".join(self.exec_command("help")).split()
else " ".join(list(self.exec_command("sn")))
)
1 change: 1 addition & 0 deletions src/NanoVNASaver/Hardware/VNA.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class VNA:
valid_datapoints: tuple[int, ...] = (101, 51, 11)
wait = 0.05
SN = "NOT SUPPORTED"
hardware_revision = "NOT SUPPORTED"
sweep_points_max = 101
sweep_points_min = 11

Expand Down
24 changes: 21 additions & 3 deletions src/NanoVNASaver/Windows/DeviceSettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,12 @@
from PySide6.QtGui import QIntValidator

from .Defaults import make_scrollable
from .Screenshot import ScreenshotWindow
from .Screenshot import ScreenshotWindow, LiveViewWindow
from .ui import get_window_icon

if TYPE_CHECKING:
from ..NanoVNASaver.NanoVNASaver import NanoVNASaver as vna_app


logger = logging.getLogger(__name__)


Expand All @@ -49,6 +48,7 @@ def __init__(self, app: "vna_app") -> None:
self.label = {
"status": QtWidgets.QLabel("Not connected."),
"firmware": QtWidgets.QLabel("Not connected."),
"hardware": QtWidgets.QLabel("Not connected."),
"calibration": QtWidgets.QLabel("Not connected."),
"SN": QtWidgets.QLabel("Not connected."),
}
Expand All @@ -65,6 +65,7 @@ def __init__(self, app: "vna_app") -> None:

status_layout.addRow("Status:", self.label["status"])
status_layout.addRow("Firmware:", self.label["firmware"])
status_layout.addRow("Hardware:", self.label["hardware"])
status_layout.addRow("Calibration:", self.label["calibration"])
status_layout.addRow("SN:", self.label["SN"])

Expand Down Expand Up @@ -95,7 +96,13 @@ def __init__(self, app: "vna_app") -> None:
self.btnCaptureScreenshot = QtWidgets.QPushButton("Screenshot")
self.btnCaptureScreenshot.clicked.connect(self.captureScreenshot)
control_layout.addWidget(self.btnCaptureScreenshot)


self.liveViewWindow = LiveViewWindow(self)
self.btnLiveView = QtWidgets.QPushButton("Live view")
self.btnLiveView.clicked.connect(self.liveView)
self.liveViewWindow.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
control_layout.addWidget(self.btnLiveView)

left_layout.addWidget(status_box)
left_layout.addLayout(control_layout)

Expand Down Expand Up @@ -139,16 +146,21 @@ def updateFields(self):
if not self.app.vna.connected():
self.label["status"].setText("Not connected.")
self.label["firmware"].setText("Not connected.")
self.label["hardware"].setText("Not connected.")
self.label["calibration"].setText("Not connected.")
self.label["SN"].setText("Not connected.")
self.featureList.clear()
self.btnCaptureScreenshot.setDisabled(True)
self.btnLiveView.setDisabled(True)
return

self.label["status"].setText(f"Connected to {self.app.vna.name}.")
self.label["firmware"].setText(
f"{self.app.vna.name} v{self.app.vna.version}"
)
self.label["hardware"].setText(
f"{self.app.vna.hardware_revision}"
)
if self.app.worker.isRunning():
self.label["calibration"].setText("(Sweep running)")
else:
Expand All @@ -160,6 +172,7 @@ def updateFields(self):
self.featureList.addItem(item)

self.btnCaptureScreenshot.setDisabled("Screenshots" not in features)
self.btnLiveView.setDisabled("Screenshots" not in features)

if "Customizable data points" in features:
self.datapoints.clear()
Expand Down Expand Up @@ -199,6 +212,11 @@ def captureScreenshot(self) -> None:
# TODO: Consider having a list of widgets that want to be
# disabled when a sweep is running?


def liveView(self) -> None:
if self.app.worker.state != SweepState.RUNNING:
self.liveViewWindow.start()

def updateNrDatapoints(self, i) -> None:
if i < 0 or self.app.worker.isRunning():
return
Expand Down
17 changes: 17 additions & 0 deletions src/NanoVNASaver/Windows/Screenshot.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,20 @@ def setScale(self, scale):
self.pix.size().height() * scale,
)
self.resize(width, height)

class LiveViewWindow(ScreenshotWindow):
def __init__(self, qtwidgets: QtWidgets.QTableWidget):
super().__init__()
self.setWindowTitle("Live View")
self.qtwidgets = qtwidgets
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_screenshot)

def start(self):
self.timer.start(2000) # Update every 2000ms (this will not burn the little chip too much on nanovna & tinysa)

def update_screenshot(self):
if self.qtwidgets.app.worker.state != SweepState.RUNNING: # Check if worker is not running
pixmap = self.qtwidgets.app.vna.getScreenshot()
self.qtwidgets.liveViewWindow.setScreenshot(pixmap)
self.qtwidgets.liveViewWindow.show()

0 comments on commit 0301103

Please sign in to comment.