1# NanoVNASaver 2# 3# A python program to view and export Touchstone data from a NanoVNA 4# Copyright (C) 2019, 2020 Rune B. Broberg 5# Copyright (C) 2020 NanoVNA-Saver Authors 6# 7# This program is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <https://www.gnu.org/licenses/>. 19import logging 20from functools import partial 21from PyQt5 import QtWidgets, QtCore 22 23from NanoVNASaver.Formatting import ( 24 format_frequency_short, format_frequency_sweep, 25) 26from NanoVNASaver.Settings.Sweep import SweepMode 27 28logger = logging.getLogger(__name__) 29 30 31class SweepSettingsWindow(QtWidgets.QWidget): 32 def __init__(self, app: QtWidgets.QWidget): 33 super().__init__() 34 self.app = app 35 self.padding = 0 36 37 self.setWindowTitle("Sweep settings") 38 self.setWindowIcon(self.app.icon) 39 40 QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, self.hide) 41 42 layout = QtWidgets.QVBoxLayout() 43 self.setLayout(layout) 44 45 layout.addWidget(self.title_box()) 46 layout.addWidget(self.settings_box()) 47 layout.addWidget(self.sweep_box()) 48 self.update_band() 49 50 def title_box(self): 51 box = QtWidgets.QGroupBox("Sweep name") 52 layout = QtWidgets.QFormLayout(box) 53 54 input_title = QtWidgets.QLineEdit(self.app.sweep.properties.name) 55 input_title.editingFinished.connect( 56 lambda: self.update_title(input_title.text())) 57 layout.addRow(input_title) 58 return box 59 60 def settings_box(self) -> 'QtWidgets.QWidget': 61 box = QtWidgets.QGroupBox("Settings") 62 layout = QtWidgets.QFormLayout(box) 63 64 # Sweep Mode 65 radio_button = QtWidgets.QRadioButton("Single sweep") 66 radio_button.setChecked( 67 self.app.sweep.properties.mode == SweepMode.SINGLE) 68 radio_button.clicked.connect( 69 lambda: self.update_mode(SweepMode.SINGLE)) 70 layout.addWidget(radio_button) 71 72 radio_button = QtWidgets.QRadioButton("Continous sweep") 73 radio_button.setChecked( 74 self.app.sweep.properties.mode == SweepMode.CONTINOUS) 75 radio_button.clicked.connect( 76 lambda: self.update_mode(SweepMode.CONTINOUS)) 77 layout.addWidget(radio_button) 78 79 radio_button = QtWidgets.QRadioButton("Averaged sweep") 80 radio_button.setChecked( 81 self.app.sweep.properties.mode == SweepMode.AVERAGE) 82 radio_button.clicked.connect( 83 lambda: self.update_mode(SweepMode.AVERAGE)) 84 layout.addWidget(radio_button) 85 86 # Log sweep 87 label = QtWidgets.QLabel( 88 "Logarithmic sweeping changes the step width in each segment" 89 " in logarithmical manner. Useful in conjunction with small" 90 " amount of datapoints and many segments. Step display in" 91 " SweepControl cannot reflect this currently.") 92 label.setWordWrap(True) 93 layout.addRow(label) 94 checkbox = QtWidgets.QCheckBox("Logarithmic sweep") 95 checkbox.setCheckState(self.app.sweep.properties.logarithmic) 96 checkbox.toggled.connect( 97 lambda: self.update_logarithmic(checkbox.isChecked())) 98 layout.addWidget(checkbox) 99 100 # Averaging 101 label = QtWidgets.QLabel( 102 "Averaging allows discarding outlying samples to get better" 103 " averages. Common values are 3/0, 5/2, 9/4 and 25/6.") 104 label.setWordWrap(True) 105 layout.addRow(label) 106 averages = QtWidgets.QLineEdit( 107 str(self.app.sweep.properties.averages[0])) 108 truncates = QtWidgets.QLineEdit( 109 str(self.app.sweep.properties.averages[1])) 110 averages.editingFinished.connect( 111 lambda: self.update_averaging(averages, truncates)) 112 truncates.editingFinished.connect( 113 lambda: self.update_averaging(averages, truncates)) 114 layout.addRow("Number of measurements to average", averages) 115 layout.addRow("Number to discard", truncates) 116 117 # TODO: is this more a device than a sweep property? 118 label = QtWidgets.QLabel( 119 "Some times when you measure amplifiers you need to use an" 120 " attenuator in line with the S21 input (CH1) here you can" 121 " specify it.") 122 label.setWordWrap(True) 123 layout.addRow(label) 124 125 input_att = QtWidgets.QLineEdit(str(self.app.s21att)) 126 input_att.editingFinished.connect( 127 lambda: self.update_attenuator(input_att)) 128 layout.addRow("Attenuator in port CH1 (s21) in dB", input_att) 129 return box 130 131 def sweep_box(self) -> 'QtWidgets.QWidget': 132 box = QtWidgets.QGroupBox("Sweep band") 133 layout = QtWidgets.QFormLayout(box) 134 135 self.band_list = QtWidgets.QComboBox() 136 self.band_list.setModel(self.app.bands) 137 # pylint: disable=unnecessary-lambda 138 self.band_list.currentIndexChanged.connect(lambda: self.update_band()) 139 layout.addRow("Select band", self.band_list) 140 141 for raw_label, btn_label, value in (("Pad band limits", "None", 0), 142 ("", "10%", 10), 143 ("", "25%", 25), 144 ("", "100%", 100),): 145 radio_button = QtWidgets.QRadioButton(btn_label) 146 radio_button.setChecked(self.padding == value) 147 radio_button.clicked.connect(partial(self.update_padding, value)) 148 layout.addRow(raw_label, radio_button) 149 150 self.band_label = QtWidgets.QLabel() 151 layout.addRow(self.band_label) 152 153 btn_set_band_sweep = QtWidgets.QPushButton("Set band sweep") 154 btn_set_band_sweep.clicked.connect(lambda: self.update_band(True)) 155 layout.addRow(btn_set_band_sweep) 156 return box 157 158 def update_band(self, apply: bool = False): 159 logger.debug("update_band(%s)", apply) 160 index_start = self.band_list.model().index(self.band_list.currentIndex(), 1) 161 index_stop = self.band_list.model().index(self.band_list.currentIndex(), 2) 162 start = int(self.band_list.model().data(index_start, QtCore.Qt.ItemDataRole).value()) 163 stop = int(self.band_list.model().data(index_stop, QtCore.Qt.ItemDataRole).value()) 164 165 if self.padding > 0: 166 span = stop - start 167 start -= round(span * self.padding / 100) 168 start = max(1, start) 169 stop += round(span * self.padding / 100) 170 171 self.band_label.setText( 172 f"Sweep span: {format_frequency_short(start)}" 173 f" to {format_frequency_short(stop)}") 174 175 if not apply: 176 return 177 178 self.app.sweep_control.input_start.setText( 179 format_frequency_sweep(start)) 180 self.app.sweep_control.input_end.setText( 181 format_frequency_sweep(stop)) 182 self.app.sweep_control.input_end.textEdited.emit( 183 self.app.sweep_control.input_end.text()) 184 185 def update_attenuator(self, value: 'QtWidgets.QLineEdit'): 186 try: 187 att = float(value.text()) 188 assert att >= 0 189 except (ValueError, AssertionError): 190 logger.warning("Values for attenuator are absolute and with no" 191 " minus sign, resetting.") 192 att = 0 193 logger.debug("Attenuator %sdB inline with S21 input", att) 194 value.setText(str(att)) 195 self.app.s21att = att 196 197 def update_averaging(self, 198 averages: 'QtWidgets.QLineEdit', 199 truncs: 'QtWidgets.QLineEdit'): 200 try: 201 amount = int(averages.text()) 202 truncates = int(truncs.text()) 203 assert amount > 0 204 assert truncates >= 0 205 assert amount > truncates 206 except (AssertionError, ValueError): 207 logger.warning("Illegal averaging values, set default") 208 amount = 3 209 truncates = 0 210 logger.debug("update_averaging(%s, %s)", amount, truncates) 211 averages.setText(str(amount)) 212 truncs.setText(str(truncates)) 213 with self.app.sweep.lock: 214 self.app.sweep.properties.averages = (amount, truncates) 215 216 def update_logarithmic(self, logarithmic: bool): 217 logger.debug("update_logarithmic(%s)", logarithmic) 218 with self.app.sweep.lock: 219 self.app.sweep.properties.logarithmic = logarithmic 220 221 def update_mode(self, mode: 'SweepMode'): 222 logger.debug("update_mode(%s)", mode) 223 with self.app.sweep.lock: 224 self.app.sweep.properties.mode = mode 225 226 def update_padding(self, padding: int): 227 logger.debug("update_padding(%s)", padding) 228 self.padding = padding 229 self.update_band() 230 231 def update_title(self, title: str = ""): 232 logger.debug("update_title(%s)", title) 233 with self.app.sweep.lock: 234 self.app.sweep.properties.name = title 235 self.app.update_sweep_title() 236