216 lines
6.5 KiB
Python
216 lines
6.5 KiB
Python
import sys
|
|
from pathlib import Path
|
|
from typing import Callable, List, Tuple
|
|
|
|
import matplotlib as mpl
|
|
import numpy as np
|
|
import xarray as xr
|
|
from matplotlib import pyplot as plt
|
|
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
|
|
from matplotlib.ticker import EngFormatter
|
|
from numpy import typing as npt
|
|
from PySide6.QtCore import QSize, Qt
|
|
from PySide6.QtGui import QAction, QKeySequence, QShortcut
|
|
from PySide6.QtWidgets import (
|
|
QApplication,
|
|
QMainWindow,
|
|
QMenu,
|
|
QProgressBar,
|
|
QPushButton,
|
|
QToolBar,
|
|
QVBoxLayout,
|
|
QWidget,
|
|
)
|
|
from skrf import plotting as rf_plt
|
|
|
|
from charon_vna.gui_helpers import FlowLayout
|
|
from charon_vna.util import db20, s2vswr
|
|
|
|
# from vna import Charon
|
|
|
|
DEFAULT_CONFIG = dict(
|
|
frequency=np.arange(1e9, 2e9, 11), # Hz
|
|
power=-5, # dB
|
|
)
|
|
|
|
|
|
class PlotWidget(QWidget):
|
|
enabled_ports: List[Tuple[int | str]]
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.enabled_ports = [(1, 1)]
|
|
|
|
layout = QVBoxLayout()
|
|
self.setLayout(layout)
|
|
|
|
self.fig = plt.Figure(figsize=(5, 4), dpi=100, tight_layout=True)
|
|
self.ax = self.fig.add_subplot(111)
|
|
self.setup_logmag()
|
|
# self.setup_smith()
|
|
self.ax.legend(loc="upper right")
|
|
|
|
canvas = FigureCanvasQTAgg(self.fig)
|
|
layout.addWidget(canvas)
|
|
|
|
# toolbar = QToolBar("Toolbar")
|
|
# toolbar.addAction("blah")
|
|
# self.addToolBar(toolbar)
|
|
|
|
def setup_rect(self) -> None:
|
|
self.ax.grid(True)
|
|
self.ax.xaxis.set_major_formatter(EngFormatter())
|
|
self.ax.set_xlabel("Frequency [Hz]")
|
|
|
|
def update_rect(self, data: xr.DataArray, func: Callable[[npt.ArrayLike], npt.ArrayLike]) -> None:
|
|
self.ax.set_xlim(data["frequency"].min().data, data["frequency"].max().data)
|
|
# remove old lines
|
|
for line in self.ax.lines:
|
|
line.remove()
|
|
for m, n in self.enabled_ports:
|
|
self.ax.plot(data["frequency"], func(data.sel(m=m, n=n)))
|
|
|
|
def setup_logmag(self, ylim: List[float] = [-30, 30]) -> None:
|
|
self.setup_rect()
|
|
self.ax.set_ylim(ylim)
|
|
self.ax.set_ylabel("Amplitude [dB]")
|
|
|
|
def update_logmag(self, data: xr.DataArray) -> None:
|
|
self.update_rect(data, db20)
|
|
|
|
def setup_phase(self) -> None:
|
|
self.setup_rect()
|
|
self.ax.set_ylim(-200, 200)
|
|
self.ax.set_ylabel("Phase [deg]")
|
|
|
|
def update_phase(self, data: xr.DataArray):
|
|
self.update_rect(data, lambda s: np.angle(s, deg=True))
|
|
|
|
def setup_vswr(self) -> None:
|
|
self.setup_rect()
|
|
self.ax.set_yticks(np.arange(1, 11))
|
|
self.ax.set_ylim(1, 10)
|
|
self.ax.set_ylabel("VSWR")
|
|
|
|
def update_vswr(self, data: xr.DataArray) -> None:
|
|
self.update_rect(data, s2vswr)
|
|
|
|
def setup_smith(self) -> None:
|
|
self.ax.grid(False)
|
|
self.ax.set_xlim(-1, 1)
|
|
self.ax.set_ylim(-1, 1)
|
|
self.ax.set_aspect("equal")
|
|
rf_plt.smith(ax=self.ax, smithR=1, chart_type="z", draw_vswr=None)
|
|
|
|
def update_smith(self, data: xr.DataArray) -> None:
|
|
# remove old lines
|
|
for line in self.ax.lines:
|
|
line.remove()
|
|
for m, n in self.enabled_ports:
|
|
sel = data.sel(m=m, n=n)
|
|
self.ax.plot(sel.real, sel.imag)
|
|
|
|
|
|
# Subclass QMainWindow to customize your application's main window
|
|
class MainWindow(QMainWindow):
|
|
config_path: Path | None
|
|
# device: Charon
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
|
|
self.config_path = None
|
|
|
|
# self.device = Charon("ip:192.168.3.1", frequency=DEFAULT_CONFIG["frequency"])
|
|
|
|
mpl.use("QtAgg")
|
|
|
|
self.setWindowTitle("Charon VNA")
|
|
|
|
# Menu
|
|
menubar = self.menuBar()
|
|
|
|
menu_file = QMenu("&File")
|
|
menubar.addMenu(menu_file)
|
|
action_load_config = QAction("&Open Configuration", self)
|
|
menu_file.addAction(action_load_config)
|
|
action_load_config.triggered.connect(self.load_config)
|
|
QShortcut(QKeySequence("Ctrl+O"), self).activated.connect(self.load_config)
|
|
action_save_config = QAction("&Save Configuration", self)
|
|
menu_file.addAction(action_save_config)
|
|
action_save_config.triggered.connect(self.save_config)
|
|
QShortcut(QKeySequence("Ctrl+S"), self).activated.connect(self.save_config)
|
|
action_saveas_config = QAction("Save Configuration &As", self)
|
|
menu_file.addAction(action_saveas_config)
|
|
action_saveas_config.triggered.connect(self.saveas_config)
|
|
QShortcut(QKeySequence("Ctrl+Shift+S"), self).activated.connect(self.saveas_config)
|
|
|
|
menu_stimulus = QMenu("&Stimulus")
|
|
menubar.addMenu(menu_stimulus)
|
|
action_set_frequency = QAction("&Frequency", self)
|
|
menu_stimulus.addAction(action_set_frequency)
|
|
# action_set_frequency.triggered.connect(self.set_frequency)
|
|
action_set_power = QAction("&Power", self)
|
|
menu_stimulus.addAction(action_set_power)
|
|
# action_set_power.triggered.connect(self.set_power)
|
|
|
|
menu_calibration = QMenu("&Calibration")
|
|
menubar.addMenu(menu_calibration)
|
|
|
|
# Content
|
|
window_layout = QVBoxLayout()
|
|
|
|
prog_sweep = QProgressBar()
|
|
prog_sweep.setMinimum(0)
|
|
prog_sweep.setMaximum(100)
|
|
prog_sweep.setTextVisible(False)
|
|
prog_sweep.setValue(50)
|
|
window_layout.addWidget(prog_sweep)
|
|
|
|
# window_widget.se
|
|
plot_layout = QVBoxLayout()
|
|
# TODO: handle plots properly
|
|
for i in range(2):
|
|
plot0 = PlotWidget()
|
|
plot_layout.addWidget(plot0)
|
|
plot_widget = QWidget()
|
|
plot_widget.setLayout(plot_layout)
|
|
window_layout.addWidget(plot_widget)
|
|
|
|
# Set the central widget of the Window.
|
|
widget = QWidget()
|
|
widget.setLayout(window_layout)
|
|
self.setCentralWidget(widget)
|
|
|
|
def saveas_config(self) -> None:
|
|
print("Prompting for save path...")
|
|
# TODO: prompt for config path
|
|
self.config_path = Path(__file__).parent / "config.json"
|
|
print(f"Config path is now {self.config_path.resolve()}")
|
|
self.save_config()
|
|
|
|
def save_config(self) -> None:
|
|
if self.config_path is None:
|
|
self.saveas_config()
|
|
else:
|
|
print(f"saving config to {self.config_path.resolve()}")
|
|
# TODO: save config
|
|
|
|
def load_config(self) -> None:
|
|
print("loading config")
|
|
# TODO: load config
|
|
|
|
|
|
def main() -> None:
|
|
app = QApplication(sys.argv)
|
|
|
|
window = MainWindow()
|
|
window.show()
|
|
|
|
app.exec()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|