1#!/usr/bin/env python 2 3import sys, signal, time 4 5from PyQt5 import QtCore, QtGui, QtWidgets 6 7from qspectrumanalyzer import backends 8from qspectrumanalyzer.version import __version__ 9from qspectrumanalyzer.data import DataStorage 10from qspectrumanalyzer.plot import SpectrumPlotWidget, WaterfallPlotWidget 11from qspectrumanalyzer.utils import color_to_str, str_to_color 12 13from qspectrumanalyzer.ui_qspectrumanalyzer_settings import Ui_QSpectrumAnalyzerSettings 14from qspectrumanalyzer.ui_qspectrumanalyzer_settings_help import Ui_QSpectrumAnalyzerSettingsHelp 15from qspectrumanalyzer.ui_qspectrumanalyzer_smooth import Ui_QSpectrumAnalyzerSmooth 16from qspectrumanalyzer.ui_qspectrumanalyzer_persistence import Ui_QSpectrumAnalyzerPersistence 17from qspectrumanalyzer.ui_qspectrumanalyzer_colors import Ui_QSpectrumAnalyzerColors 18from qspectrumanalyzer.ui_qspectrumanalyzer import Ui_QSpectrumAnalyzerMainWindow 19 20# Allow CTRL+C and/or SIGTERM to kill us (PyQt blocks it otherwise) 21signal.signal(signal.SIGINT, signal.SIG_DFL) 22signal.signal(signal.SIGTERM, signal.SIG_DFL) 23 24 25class QSpectrumAnalyzerSettings(QtWidgets.QDialog, Ui_QSpectrumAnalyzerSettings): 26 """QSpectrumAnalyzer settings dialog""" 27 def __init__(self, parent=None): 28 # Initialize UI 29 super().__init__(parent) 30 self.setupUi(self) 31 self.params_help_dialog = None 32 self.device_help_dialog = None 33 34 # Load settings 35 settings = QtCore.QSettings() 36 self.executableEdit.setText(settings.value("executable", "soapy_power")) 37 self.deviceEdit.setText(settings.value("device", "")) 38 self.lnbSpinBox.setValue(settings.value("lnb_lo", 0, float) / 1e6) 39 self.waterfallHistorySizeSpinBox.setValue(settings.value("waterfall_history_size", 100, int)) 40 41 backend = settings.value("backend", "soapy_power") 42 try: 43 backend_module = getattr(backends, backend) 44 except AttributeError: 45 backend_module = backends.soapy_power 46 47 self.paramsEdit.setText(settings.value("params", backend_module.Info.additional_params)) 48 self.deviceHelpButton.setEnabled(bool(backend_module.Info.help_device)) 49 50 self.sampleRateSpinBox.setMinimum(backend_module.Info.sample_rate_min / 1e6) 51 self.sampleRateSpinBox.setMaximum(backend_module.Info.sample_rate_max / 1e6) 52 self.sampleRateSpinBox.setValue(settings.value("sample_rate", backend_module.Info.sample_rate, float) / 1e6) 53 54 self.bandwidthSpinBox.setMinimum(backend_module.Info.bandwidth_min / 1e6) 55 self.bandwidthSpinBox.setMaximum(backend_module.Info.bandwidth_max / 1e6) 56 self.bandwidthSpinBox.setValue(settings.value("bandwidth", backend_module.Info.bandwidth, float) / 1e6) 57 58 self.backendComboBox.blockSignals(True) 59 self.backendComboBox.clear() 60 for b in sorted(backends.__all__): 61 self.backendComboBox.addItem(b) 62 63 i = self.backendComboBox.findText(backend) 64 if i == -1: 65 self.backendComboBox.setCurrentIndex(0) 66 else: 67 self.backendComboBox.setCurrentIndex(i) 68 self.backendComboBox.blockSignals(False) 69 70 @QtCore.pyqtSlot() 71 def on_executableButton_clicked(self): 72 """Open file dialog when button is clicked""" 73 filename = QtWidgets.QFileDialog.getOpenFileName(self, self.tr("Select executable - QSpectrumAnalyzer"))[0] 74 if filename: 75 self.executableEdit.setText(filename) 76 77 @QtCore.pyqtSlot() 78 def on_paramsHelpButton_clicked(self): 79 """Open additional parameters help dialog when button is clicked""" 80 try: 81 backend_module = getattr(backends, self.backendComboBox.currentText()) 82 except AttributeError: 83 backend_module = backends.soapy_power 84 85 self.params_help_dialog = QSpectrumAnalyzerSettingsHelp( 86 backend_module.Info.help_params(self.executableEdit.text()), 87 parent=self 88 ) 89 90 self.params_help_dialog.show() 91 self.params_help_dialog.raise_() 92 self.params_help_dialog.activateWindow() 93 94 @QtCore.pyqtSlot() 95 def on_deviceHelpButton_clicked(self): 96 """Open device help dialog when button is clicked""" 97 try: 98 backend_module = getattr(backends, self.backendComboBox.currentText()) 99 except AttributeError: 100 backend_module = backends.soapy_power 101 102 self.device_help_dialog = QSpectrumAnalyzerSettingsHelp( 103 backend_module.Info.help_device(self.executableEdit.text(), self.deviceEdit.text()), 104 parent=self 105 ) 106 107 self.device_help_dialog.show() 108 self.device_help_dialog.raise_() 109 self.device_help_dialog.activateWindow() 110 111 @QtCore.pyqtSlot(str) 112 def on_backendComboBox_currentIndexChanged(self, text): 113 """Change executable when backend is changed""" 114 self.executableEdit.setText(text) 115 self.deviceEdit.setText("") 116 117 try: 118 backend_module = getattr(backends, text) 119 except AttributeError: 120 backend_module = backends.soapy_power 121 122 self.paramsEdit.setText(backend_module.Info.additional_params) 123 self.deviceHelpButton.setEnabled(bool(backend_module.Info.help_device)) 124 self.sampleRateSpinBox.setMinimum(backend_module.Info.sample_rate_min / 1e6) 125 self.sampleRateSpinBox.setMaximum(backend_module.Info.sample_rate_max / 1e6) 126 self.sampleRateSpinBox.setValue(backend_module.Info.sample_rate / 1e6) 127 self.bandwidthSpinBox.setMinimum(backend_module.Info.bandwidth_min / 1e6) 128 self.bandwidthSpinBox.setMaximum(backend_module.Info.bandwidth_max / 1e6) 129 self.bandwidthSpinBox.setValue(backend_module.Info.bandwidth / 1e6) 130 131 def accept(self): 132 """Save settings when dialog is accepted""" 133 settings = QtCore.QSettings() 134 settings.setValue("backend", self.backendComboBox.currentText()) 135 settings.setValue("executable", self.executableEdit.text()) 136 settings.setValue("params", self.paramsEdit.text()) 137 settings.setValue("device", self.deviceEdit.text()) 138 settings.setValue("sample_rate", self.sampleRateSpinBox.value() * 1e6) 139 settings.setValue("bandwidth", self.bandwidthSpinBox.value() * 1e6) 140 settings.setValue("lnb_lo", self.lnbSpinBox.value() * 1e6) 141 settings.setValue("waterfall_history_size", self.waterfallHistorySizeSpinBox.value()) 142 QtWidgets.QDialog.accept(self) 143 144 145class QSpectrumAnalyzerSettingsHelp(QtWidgets.QDialog, Ui_QSpectrumAnalyzerSettingsHelp): 146 """QSpectrumAnalyzer settings help dialog""" 147 def __init__(self, text, parent=None): 148 # Initialize UI 149 super().__init__(parent) 150 self.setupUi(self) 151 152 monospace_font = QtGui.QFont('monospace') 153 monospace_font.setStyleHint(QtGui.QFont.Monospace) 154 self.helpTextEdit.setFont(monospace_font) 155 self.helpTextEdit.setPlainText(text) 156 157 158class QSpectrumAnalyzerSmooth(QtWidgets.QDialog, Ui_QSpectrumAnalyzerSmooth): 159 """QSpectrumAnalyzer spectrum smoothing dialog""" 160 def __init__(self, parent=None): 161 # Initialize UI 162 super().__init__(parent) 163 self.setupUi(self) 164 165 # Load settings 166 settings = QtCore.QSettings() 167 self.windowLengthSpinBox.setValue(settings.value("smooth_length", 11, int)) 168 169 window_function = settings.value("smooth_window", "hanning") 170 i = self.windowFunctionComboBox.findText(window_function) 171 if i == -1: 172 self.windowFunctionComboBox.setCurrentIndex(0) 173 else: 174 self.windowFunctionComboBox.setCurrentIndex(i) 175 176 def accept(self): 177 """Save settings when dialog is accepted""" 178 settings = QtCore.QSettings() 179 settings.setValue("smooth_length", self.windowLengthSpinBox.value()) 180 settings.setValue("smooth_window", self.windowFunctionComboBox.currentText()) 181 QtWidgets.QDialog.accept(self) 182 183 184class QSpectrumAnalyzerPersistence(QtWidgets.QDialog, Ui_QSpectrumAnalyzerPersistence): 185 """QSpectrumAnalyzer spectrum persistence dialog""" 186 def __init__(self, parent=None): 187 # Initialize UI 188 super().__init__(parent) 189 self.setupUi(self) 190 191 # Load settings 192 settings = QtCore.QSettings() 193 self.persistenceLengthSpinBox.setValue(settings.value("persistence_length", 5, int)) 194 195 decay_function = settings.value("persistence_decay", "exponential") 196 i = self.decayFunctionComboBox.findText(decay_function) 197 if i == -1: 198 self.decayFunctionComboBox.setCurrentIndex(0) 199 else: 200 self.decayFunctionComboBox.setCurrentIndex(i) 201 202 def accept(self): 203 """Save settings when dialog is accepted""" 204 settings = QtCore.QSettings() 205 settings.setValue("persistence_length", self.persistenceLengthSpinBox.value()) 206 settings.setValue("persistence_decay", self.decayFunctionComboBox.currentText()) 207 QtWidgets.QDialog.accept(self) 208 209 210class QSpectrumAnalyzerColors(QtWidgets.QDialog, Ui_QSpectrumAnalyzerColors): 211 """QSpectrumAnalyzer colors dialog""" 212 def __init__(self, parent=None): 213 # Initialize UI 214 super().__init__(parent) 215 self.setupUi(self) 216 217 # Load settings 218 settings = QtCore.QSettings() 219 self.mainColorButton.setColor(str_to_color(settings.value("main_color", "255, 255, 0, 255"))) 220 self.peakHoldMaxColorButton.setColor(str_to_color(settings.value("peak_hold_max_color", "255, 0, 0, 255"))) 221 self.peakHoldMinColorButton.setColor(str_to_color(settings.value("peak_hold_min_color", "0, 0, 255, 255"))) 222 self.averageColorButton.setColor(str_to_color(settings.value("average_color", "0, 255, 255, 255"))) 223 self.persistenceColorButton.setColor(str_to_color(settings.value("persistence_color", "0, 255, 0, 255"))) 224 225 def accept(self): 226 """Save settings when dialog is accepted""" 227 settings = QtCore.QSettings() 228 settings.setValue("main_color", color_to_str(self.mainColorButton.color())) 229 settings.setValue("peak_hold_max_color", color_to_str(self.peakHoldMaxColorButton.color())) 230 settings.setValue("peak_hold_min_color", color_to_str(self.peakHoldMinColorButton.color())) 231 settings.setValue("average_color", color_to_str(self.averageColorButton.color())) 232 settings.setValue("persistence_color", color_to_str(self.persistenceColorButton.color())) 233 QtWidgets.QDialog.accept(self) 234 235 236class QSpectrumAnalyzerMainWindow(QtWidgets.QMainWindow, Ui_QSpectrumAnalyzerMainWindow): 237 """QSpectrumAnalyzer main window""" 238 def __init__(self, parent=None): 239 # Initialize UI 240 super().__init__(parent) 241 self.setupUi(self) 242 243 # Create plot widgets and update UI 244 self.spectrumPlotWidget = SpectrumPlotWidget(self.mainPlotLayout) 245 self.waterfallPlotWidget = WaterfallPlotWidget(self.waterfallPlotLayout, self.histogramPlotLayout) 246 247 # Link main spectrum plot to waterfall plot 248 self.spectrumPlotWidget.plot.setXLink(self.waterfallPlotWidget.plot) 249 250 # Setup power thread and connect signals 251 self.prev_data_timestamp = None 252 self.data_storage = None 253 self.power_thread = None 254 self.backend = None 255 self.setup_power_thread() 256 257 self.update_buttons() 258 self.load_settings() 259 260 def setup_power_thread(self): 261 """Create power_thread and connect signals to slots""" 262 if self.power_thread: 263 self.stop() 264 265 settings = QtCore.QSettings() 266 self.data_storage = DataStorage(max_history_size=settings.value("waterfall_history_size", 100, int)) 267 self.data_storage.data_updated.connect(self.update_data) 268 self.data_storage.data_updated.connect(self.spectrumPlotWidget.update_plot) 269 self.data_storage.data_updated.connect(self.spectrumPlotWidget.update_persistence) 270 self.data_storage.data_recalculated.connect(self.spectrumPlotWidget.recalculate_plot) 271 self.data_storage.data_recalculated.connect(self.spectrumPlotWidget.recalculate_persistence) 272 self.data_storage.history_updated.connect(self.waterfallPlotWidget.update_plot) 273 self.data_storage.average_updated.connect(self.spectrumPlotWidget.update_average) 274 self.data_storage.peak_hold_max_updated.connect(self.spectrumPlotWidget.update_peak_hold_max) 275 self.data_storage.peak_hold_min_updated.connect(self.spectrumPlotWidget.update_peak_hold_min) 276 277 # Setup default values and limits in case that backend is changed 278 backend = settings.value("backend", "soapy_power") 279 try: 280 backend_module = getattr(backends, backend) 281 except AttributeError: 282 backend_module = backends.soapy_power 283 284 if self.backend is None or backend != self.backend: 285 self.backend = backend 286 self.gainSpinBox.setMinimum(backend_module.Info.gain_min) 287 self.gainSpinBox.setMaximum(backend_module.Info.gain_max) 288 self.gainSpinBox.setValue(backend_module.Info.gain) 289 self.startFreqSpinBox.setMinimum(backend_module.Info.start_freq_min) 290 self.startFreqSpinBox.setMaximum(backend_module.Info.start_freq_max) 291 self.startFreqSpinBox.setValue(backend_module.Info.start_freq) 292 self.stopFreqSpinBox.setMinimum(backend_module.Info.stop_freq_min) 293 self.stopFreqSpinBox.setMaximum(backend_module.Info.stop_freq_max) 294 self.stopFreqSpinBox.setValue(backend_module.Info.stop_freq) 295 self.binSizeSpinBox.setMinimum(backend_module.Info.bin_size_min) 296 self.binSizeSpinBox.setMaximum(backend_module.Info.bin_size_max) 297 self.binSizeSpinBox.setValue(backend_module.Info.bin_size) 298 self.intervalSpinBox.setMinimum(backend_module.Info.interval_min) 299 self.intervalSpinBox.setMaximum(backend_module.Info.interval_max) 300 self.intervalSpinBox.setValue(backend_module.Info.interval) 301 self.ppmSpinBox.setMinimum(backend_module.Info.ppm_min) 302 self.ppmSpinBox.setMaximum(backend_module.Info.ppm_max) 303 self.ppmSpinBox.setValue(backend_module.Info.ppm) 304 self.cropSpinBox.setMinimum(backend_module.Info.crop_min) 305 self.cropSpinBox.setMaximum(backend_module.Info.crop_max) 306 self.cropSpinBox.setValue(backend_module.Info.crop) 307 308 # Setup default values and limits in case that LNB LO is changed 309 lnb_lo = settings.value("lnb_lo", 0, float) / 1e6 310 311 start_freq_min = backend_module.Info.start_freq_min + lnb_lo 312 start_freq_max = backend_module.Info.start_freq_max + lnb_lo 313 start_freq = self.startFreqSpinBox.value() 314 stop_freq_min = backend_module.Info.stop_freq_min + lnb_lo 315 stop_freq_max = backend_module.Info.stop_freq_max + lnb_lo 316 stop_freq = self.stopFreqSpinBox.value() 317 318 self.startFreqSpinBox.setMinimum(start_freq_min if start_freq_min > 0 else 0) 319 self.startFreqSpinBox.setMaximum(start_freq_max) 320 if start_freq < start_freq_min or start_freq > start_freq_max: 321 self.startFreqSpinBox.setValue(start_freq_min) 322 323 self.stopFreqSpinBox.setMinimum(stop_freq_min if stop_freq_min > 0 else 0) 324 self.stopFreqSpinBox.setMaximum(stop_freq_max) 325 if stop_freq < stop_freq_min or stop_freq > stop_freq_max: 326 self.stopFreqSpinBox.setValue(stop_freq_max) 327 328 self.power_thread = backend_module.PowerThread(self.data_storage) 329 self.power_thread.powerThreadStarted.connect(self.update_buttons) 330 self.power_thread.powerThreadStopped.connect(self.update_buttons) 331 332 def set_dock_size(self, dock, width, height): 333 """Ugly hack for resizing QDockWidget (because it doesn't respect minimumSize / sizePolicy set in Designer) 334 Link: https://stackoverflow.com/questions/2722939/c-resize-a-docked-qt-qdockwidget-programmatically""" 335 old_min_size = dock.minimumSize() 336 old_max_size = dock.maximumSize() 337 338 if width >= 0: 339 if dock.width() < width: 340 dock.setMinimumWidth(width) 341 else: 342 dock.setMaximumWidth(width) 343 344 if height >= 0: 345 if dock.height() < height: 346 dock.setMinimumHeight(height) 347 else: 348 dock.setMaximumHeight(height) 349 350 QtCore.QTimer.singleShot(0, lambda: self.set_dock_size_callback(dock, old_min_size, old_max_size)) 351 352 def set_dock_size_callback(self, dock, old_min_size, old_max_size): 353 """Return to original QDockWidget minimumSize and maximumSize after running set_dock_size()""" 354 dock.setMinimumSize(old_min_size) 355 dock.setMaximumSize(old_max_size) 356 357 def load_settings(self): 358 """Restore spectrum analyzer settings and window geometry""" 359 settings = QtCore.QSettings() 360 self.startFreqSpinBox.setValue(settings.value("start_freq", 87.0, float)) 361 self.stopFreqSpinBox.setValue(settings.value("stop_freq", 108.0, float)) 362 self.binSizeSpinBox.setValue(settings.value("bin_size", 10.0, float)) 363 self.intervalSpinBox.setValue(settings.value("interval", 10.0, float)) 364 self.gainSpinBox.setValue(settings.value("gain", 0, int)) 365 self.ppmSpinBox.setValue(settings.value("ppm", 0, int)) 366 self.cropSpinBox.setValue(settings.value("crop", 0, int)) 367 self.mainCurveCheckBox.setChecked(settings.value("main_curve", 1, int)) 368 self.peakHoldMaxCheckBox.setChecked(settings.value("peak_hold_max", 0, int)) 369 self.peakHoldMinCheckBox.setChecked(settings.value("peak_hold_min", 0, int)) 370 self.averageCheckBox.setChecked(settings.value("average", 0, int)) 371 self.smoothCheckBox.setChecked(settings.value("smooth", 0, int)) 372 self.persistenceCheckBox.setChecked(settings.value("persistence", 0, int)) 373 374 # Restore window state 375 if settings.value("window_state"): 376 self.restoreState(settings.value("window_state")) 377 if settings.value("plotsplitter_state"): 378 self.plotSplitter.restoreState(settings.value("plotsplitter_state")) 379 380 # Migration from older version of config file 381 if settings.value("config_version", 1, int) < 2: 382 # Make tabs from docks when started for first time 383 self.tabifyDockWidget(self.settingsDockWidget, self.levelsDockWidget) 384 self.settingsDockWidget.raise_() 385 self.set_dock_size(self.controlsDockWidget, 0, 0) 386 self.set_dock_size(self.frequencyDockWidget, 0, 0) 387 # Update config version 388 settings.setValue("config_version", 2) 389 390 # Window geometry has to be restored only after show(), because initial 391 # maximization doesn't work otherwise (at least not in some window managers on X11) 392 self.show() 393 if settings.value("window_geometry"): 394 self.restoreGeometry(settings.value("window_geometry")) 395 396 def save_settings(self): 397 """Save spectrum analyzer settings and window geometry""" 398 settings = QtCore.QSettings() 399 settings.setValue("start_freq", self.startFreqSpinBox.value()) 400 settings.setValue("stop_freq", self.stopFreqSpinBox.value()) 401 settings.setValue("bin_size", self.binSizeSpinBox.value()) 402 settings.setValue("interval", self.intervalSpinBox.value()) 403 settings.setValue("gain", self.gainSpinBox.value()) 404 settings.setValue("ppm", self.ppmSpinBox.value()) 405 settings.setValue("crop", self.cropSpinBox.value()) 406 settings.setValue("main_curve", int(self.mainCurveCheckBox.isChecked())) 407 settings.setValue("peak_hold_max", int(self.peakHoldMaxCheckBox.isChecked())) 408 settings.setValue("peak_hold_min", int(self.peakHoldMinCheckBox.isChecked())) 409 settings.setValue("average", int(self.averageCheckBox.isChecked())) 410 settings.setValue("smooth", int(self.smoothCheckBox.isChecked())) 411 settings.setValue("persistence", int(self.persistenceCheckBox.isChecked())) 412 413 # Save window state and geometry 414 settings.setValue("window_geometry", self.saveGeometry()) 415 settings.setValue("window_state", self.saveState()) 416 settings.setValue("plotsplitter_state", self.plotSplitter.saveState()) 417 418 def show_status(self, message, timeout=2000): 419 """Show message in status bar""" 420 self.statusbar.showMessage(message, timeout) 421 422 def update_buttons(self): 423 """Update state of control buttons""" 424 self.startButton.setEnabled(not self.power_thread.alive) 425 self.singleShotButton.setEnabled(not self.power_thread.alive) 426 self.stopButton.setEnabled(self.power_thread.alive) 427 428 def update_data(self, data_storage): 429 """Update GUI when new data is received""" 430 # Show number of hops and how much time did the sweep really take 431 timestamp = time.time() 432 sweep_time = timestamp - self.prev_data_timestamp 433 self.prev_data_timestamp = timestamp 434 435 status = [] 436 if self.power_thread.params["hops"]: 437 status.append(self.tr("Frequency hops: {}").format(self.power_thread.params["hops"])) 438 status.append(self.tr("Sweep time: {:.2f} s | FPS: {:.2f}").format(sweep_time, 1 / sweep_time)) 439 self.show_status(" | ".join(status), timeout=0) 440 441 def start(self, single_shot=False): 442 """Start power thread""" 443 settings = QtCore.QSettings() 444 self.prev_data_timestamp = time.time() 445 446 self.data_storage.reset() 447 self.data_storage.set_smooth( 448 bool(self.smoothCheckBox.isChecked()), 449 settings.value("smooth_length", 11, int), 450 settings.value("smooth_window", "hanning"), 451 recalculate=False 452 ) 453 454 self.waterfallPlotWidget.history_size = settings.value("waterfall_history_size", 100, int) 455 self.waterfallPlotWidget.clear_plot() 456 457 self.spectrumPlotWidget.main_curve = bool(self.mainCurveCheckBox.isChecked()) 458 self.spectrumPlotWidget.main_color = str_to_color(settings.value("main_color", "255, 255, 0, 255")) 459 self.spectrumPlotWidget.peak_hold_max = bool(self.peakHoldMaxCheckBox.isChecked()) 460 self.spectrumPlotWidget.peak_hold_max_color = str_to_color(settings.value("peak_hold_max_color", "255, 0, 0, 255")) 461 self.spectrumPlotWidget.peak_hold_min = bool(self.peakHoldMinCheckBox.isChecked()) 462 self.spectrumPlotWidget.peak_hold_min_color = str_to_color(settings.value("peak_hold_min_color", "0, 0, 255, 255")) 463 self.spectrumPlotWidget.average = bool(self.averageCheckBox.isChecked()) 464 self.spectrumPlotWidget.average_color = str_to_color(settings.value("average_color", "0, 255, 255, 255")) 465 self.spectrumPlotWidget.persistence = bool(self.persistenceCheckBox.isChecked()) 466 self.spectrumPlotWidget.persistence_length = settings.value("persistence_length", 5, int) 467 self.spectrumPlotWidget.persistence_decay = settings.value("persistence_decay", "exponential") 468 self.spectrumPlotWidget.persistence_color = str_to_color(settings.value("persistence_color", "0, 255, 0, 255")) 469 self.spectrumPlotWidget.clear_plot() 470 self.spectrumPlotWidget.clear_peak_hold_max() 471 self.spectrumPlotWidget.clear_peak_hold_min() 472 self.spectrumPlotWidget.clear_average() 473 self.spectrumPlotWidget.clear_persistence() 474 475 if not self.power_thread.alive: 476 self.power_thread.setup(float(self.startFreqSpinBox.value()), 477 float(self.stopFreqSpinBox.value()), 478 float(self.binSizeSpinBox.value()), 479 interval=float(self.intervalSpinBox.value()), 480 gain=int(self.gainSpinBox.value()), 481 ppm=int(self.ppmSpinBox.value()), 482 crop=int(self.cropSpinBox.value()) / 100.0, 483 single_shot=single_shot, 484 device=settings.value("device", ""), 485 sample_rate=settings.value("sample_rate", 2560000, float), 486 bandwidth=settings.value("bandwidth", 0, float), 487 lnb_lo=settings.value("lnb_lo", 0, float)) 488 self.power_thread.start() 489 490 def stop(self): 491 """Stop power thread""" 492 if self.power_thread.alive: 493 self.power_thread.stop() 494 495 @QtCore.pyqtSlot() 496 def on_startButton_clicked(self): 497 self.start() 498 499 @QtCore.pyqtSlot() 500 def on_singleShotButton_clicked(self): 501 self.start(single_shot=True) 502 503 @QtCore.pyqtSlot() 504 def on_stopButton_clicked(self): 505 self.stop() 506 507 @QtCore.pyqtSlot(bool) 508 def on_mainCurveCheckBox_toggled(self, checked): 509 self.spectrumPlotWidget.main_curve = checked 510 if self.spectrumPlotWidget.curve.xData is None: 511 self.spectrumPlotWidget.update_plot(self.data_storage) 512 self.spectrumPlotWidget.curve.setVisible(checked) 513 514 @QtCore.pyqtSlot(bool) 515 def on_peakHoldMaxCheckBox_toggled(self, checked): 516 self.spectrumPlotWidget.peak_hold_max = checked 517 if self.spectrumPlotWidget.curve_peak_hold_max.xData is None: 518 self.spectrumPlotWidget.update_peak_hold_max(self.data_storage) 519 self.spectrumPlotWidget.curve_peak_hold_max.setVisible(checked) 520 521 @QtCore.pyqtSlot(bool) 522 def on_peakHoldMinCheckBox_toggled(self, checked): 523 self.spectrumPlotWidget.peak_hold_min = checked 524 if self.spectrumPlotWidget.curve_peak_hold_min.xData is None: 525 self.spectrumPlotWidget.update_peak_hold_min(self.data_storage) 526 self.spectrumPlotWidget.curve_peak_hold_min.setVisible(checked) 527 528 @QtCore.pyqtSlot(bool) 529 def on_averageCheckBox_toggled(self, checked): 530 self.spectrumPlotWidget.average = checked 531 if self.spectrumPlotWidget.curve_average.xData is None: 532 self.spectrumPlotWidget.update_average(self.data_storage) 533 self.spectrumPlotWidget.curve_average.setVisible(checked) 534 535 @QtCore.pyqtSlot(bool) 536 def on_persistenceCheckBox_toggled(self, checked): 537 self.spectrumPlotWidget.persistence = checked 538 if self.spectrumPlotWidget.persistence_curves[0].xData is None: 539 self.spectrumPlotWidget.recalculate_persistence(self.data_storage) 540 for curve in self.spectrumPlotWidget.persistence_curves: 541 curve.setVisible(checked) 542 543 @QtCore.pyqtSlot(bool) 544 def on_smoothCheckBox_toggled(self, checked): 545 settings = QtCore.QSettings() 546 self.data_storage.set_smooth( 547 checked, 548 settings.value("smooth_length", 11, int), 549 settings.value("smooth_window", "hanning"), 550 recalculate=True 551 ) 552 553 @QtCore.pyqtSlot() 554 def on_smoothButton_clicked(self): 555 dialog = QSpectrumAnalyzerSmooth(self) 556 if dialog.exec_(): 557 settings = QtCore.QSettings() 558 self.data_storage.set_smooth( 559 bool(self.smoothCheckBox.isChecked()), 560 settings.value("smooth_length", 11, int), 561 settings.value("smooth_window", "hanning"), 562 recalculate=True 563 ) 564 565 @QtCore.pyqtSlot() 566 def on_persistenceButton_clicked(self): 567 prev_persistence_length = self.spectrumPlotWidget.persistence_length 568 dialog = QSpectrumAnalyzerPersistence(self) 569 if dialog.exec_(): 570 settings = QtCore.QSettings() 571 persistence_length = settings.value("persistence_length", 5, int) 572 self.spectrumPlotWidget.persistence_length = persistence_length 573 self.spectrumPlotWidget.persistence_decay = settings.value("persistence_decay", "exponential") 574 575 # If only decay function has been changed, just reset colors 576 if persistence_length == prev_persistence_length: 577 self.spectrumPlotWidget.set_colors() 578 else: 579 self.spectrumPlotWidget.recalculate_persistence(self.data_storage) 580 581 @QtCore.pyqtSlot() 582 def on_colorsButton_clicked(self): 583 dialog = QSpectrumAnalyzerColors(self) 584 if dialog.exec_(): 585 settings = QtCore.QSettings() 586 self.spectrumPlotWidget.main_color = str_to_color(settings.value("main_color", "255, 255, 0, 255")) 587 self.spectrumPlotWidget.peak_hold_max_color = str_to_color(settings.value("peak_hold_max_color", "255, 0, 0, 255")) 588 self.spectrumPlotWidget.peak_hold_min_color = str_to_color(settings.value("peak_hold_min_color", "0, 0, 255, 255")) 589 self.spectrumPlotWidget.average_color = str_to_color(settings.value("average_color", "0, 255, 255, 255")) 590 self.spectrumPlotWidget.persistence_color = str_to_color(settings.value("persistence_color", "0, 255, 0, 255")) 591 self.spectrumPlotWidget.set_colors() 592 593 @QtCore.pyqtSlot() 594 def on_action_Settings_triggered(self): 595 dialog = QSpectrumAnalyzerSettings(self) 596 if dialog.exec_(): 597 self.setup_power_thread() 598 599 @QtCore.pyqtSlot() 600 def on_action_About_triggered(self): 601 QtWidgets.QMessageBox.information(self, self.tr("About - QSpectrumAnalyzer"), 602 self.tr("QSpectrumAnalyzer {}").format(__version__)) 603 604 @QtCore.pyqtSlot() 605 def on_action_Quit_triggered(self): 606 self.close() 607 608 def closeEvent(self, event): 609 """Save settings when main window is closed""" 610 self.stop() 611 self.save_settings() 612 613 614def main(): 615 app = QtWidgets.QApplication(sys.argv) 616 app.setOrganizationName("QSpectrumAnalyzer") 617 app.setOrganizationDomain("qspectrumanalyzer.eutopia.cz") 618 app.setApplicationName("QSpectrumAnalyzer") 619 window = QSpectrumAnalyzerMainWindow() 620 sys.exit(app.exec_()) 621 622 623if __name__ == "__main__": 624 main() 625