New version 1.5.0 (soapy_sdr backend now fully functioning)

This commit is contained in:
Michal Krenek (Mikos) 2017-03-10 17:39:04 +01:00
parent dc27e90653
commit 47ea0b99b5
17 changed files with 439 additions and 136 deletions

View File

@ -1,14 +1,20 @@
# Maintainer: Michal Krenek (Mikos) <m.krenek@gmail.com> # Maintainer: Michal Krenek (Mikos) <m.krenek@gmail.com>
pkgname=qspectrumanalyzer pkgname=qspectrumanalyzer
pkgver=1.4.0 pkgver=1.5.0
pkgrel=1 pkgrel=1
pkgdesc="Spectrum analyzer for RTL-SDR (GUI for rtl_power based on PyQtGraph)" pkgdesc="Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power, rx_power, rtl_power, hackrf_sweep and other backends)"
arch=('any') arch=('any')
url="https://github.com/xmikos/qspectrumanalyzer" url="https://github.com/xmikos/qspectrumanalyzer"
license=('GPL3') license=('GPL3')
depends=('python-pyqt4' 'python-pyqtgraph' 'rtl-sdr') depends=('python-pyqt4' 'python-pyqtgraph' 'soapy_power')
makedepends=('python-setuptools') makedepends=('python-setuptools')
optdepends=('rtl_power_fftw-git: alternative rtl_power implementation using FFTW library') optdepends=(
'rtl_power_fftw-git: alternative RTL-SDR backend using FFTW library (much faster than rtl_power)'
'rtl-sdr-keenerd-git: better version of rtl_power backend'
'rtl-sdr: original rtl_power backend (slightly broken, use rtl-sdr-keenerd-git instead)'
'rx_tools: rx_power backend (universal SoapySDR based backend, but seems slow and buggy)'
'hackrf: hackrf_sweep backend (wideband spectrum monitoring with sweep rate of 8 GHz/s)'
)
source=(https://github.com/xmikos/qspectrumanalyzer/archive/v$pkgver.tar.gz) source=(https://github.com/xmikos/qspectrumanalyzer/archive/v$pkgver.tar.gz)
build() { build() {

View File

@ -17,7 +17,8 @@ Requirements
- Python >= 3.3 - Python >= 3.3
- PyQt >= 4.5 - PyQt >= 4.5
- PyQtGraph (http://www.pyqtgraph.org) - PyQtGraph (http://www.pyqtgraph.org)
- soapy_power / rx_tools / rtl-sdr / rtl_power_fftw / hackrf - soapy_power (https://github.com/xmikos/soapy_power)
- Optional: rx_tools / rtl-sdr / rtl_power_fftw / hackrf
Backends Backends
-------- --------
@ -36,7 +37,7 @@ USRP and some other SDR devices).
``rx_power`` (part of ``rx_tools``) is also based on SoapySDR and therefore ``rx_power`` (part of ``rx_tools``) is also based on SoapySDR and therefore
supports nearly all SDR platforms, but it is much slower than soapy_power, doesn't support supports nearly all SDR platforms, but it is much slower than soapy_power, doesn't support
near real-time continuous measurement (minimum interval is 1 second - same as ``rtl_power``) near real-time continuous measurement (minimum interval is 1 second, same as ``rtl_power``)
and is little buggy. and is little buggy.
RTL-SDR backends RTL-SDR backends
@ -73,14 +74,14 @@ Usage
Start QSpectrumAnalyzer by running ``qspectrumanalyzer``. Start QSpectrumAnalyzer by running ``qspectrumanalyzer``.
You can choose which backend you want to use in *File* -> *Settings* You can choose which backend you want to use in *File* -> *Settings*
(default is ``soapy_power``). Sample rate and path to backend executable (default is ``soapy_power``). Sample rate, path to backend executable
can be also manually specified there. You can also set waterfall plot and additional backend parameters can be also manually specified there.
history size. Default is 100 lines, be aware that really large sweeps You can also set waterfall plot history size. Default is 100 lines, be aware
(with a lot of bins) would require a lot of system memory, that really large sweeps (with a lot of bins) would require a lot of system
so don't make this number too big. memory, so don't make this number too big.
Controls should be intuitive, but if you want consistent results, you should Controls should be intuitive, but if you want consistent results, you should
turn off automatic gain control (set it to some fixed number) and also set turn off automatic gain control (set gain to some fixed number) and also set
crop to 20% or more. For finding out ppm correction factor for your rtl-sdr crop to 20% or more. For finding out ppm correction factor for your rtl-sdr
stick, use `kalibrate-rtl <https://github.com/steve-m/kalibrate-rtl>`_. stick, use `kalibrate-rtl <https://github.com/steve-m/kalibrate-rtl>`_.
@ -108,17 +109,30 @@ Git master branch:
cd qspectrumanalyzer-git cd qspectrumanalyzer-git
makepkg -sri makepkg -sri
Or simply use `pacaur <https://aur.archlinux.org/packages/pacaur>`_ (or any other AUR helper): Or simply use `pacaur <https://aur.archlinux.org/packages/pacaur>`_ (or any other AUR helper)
which will also automatically install all QSpectrumAnalyzer dependencies:
:: ::
pacaur -S qspectrumanalyzer pacaur -S qspectrumanalyzer
pacaur -S qspectrumanalyzer-git pacaur -S qspectrumanalyzer-git
Debian / Ubuntu: Ubuntu:
**************** *******
:: ::
sudo apt-get install python3-pip python3-pyqt4 python3-numpy # Add SoapySDR PPA to your system
sudo add-apt-repository -y ppa:myriadrf/drivers
# Update list of packages
sudo apt-get update
# Install basic dependencies
sudo apt-get install python3-pip python3-pyqt4 python3-numpy soapysdr python3-soapysdr
# Install SoapySDR drivers for your hardware (e.g. RTL-SDR, Airspy, HackRF, LimeSDR, etc.)
sudo apt-get install soapysdr-module-rtlsdr soapysdr-module-airspy soapysdr-module-hackrf soapysdr-module-lms7
# Install QSpectrumAnalyzer
sudo pip3 install qspectrumanalyzer sudo pip3 install qspectrumanalyzer
Warning! ``pip`` will install packages system-wide by default, but you Warning! ``pip`` will install packages system-wide by default, but you
@ -142,7 +156,6 @@ If you want to install QSpectrumAnalyzer directly from Git master branch, you ca
Todo: Todo:
----- -----
- finish soapy_power backend (new universal default backend)
- show scan progress - show scan progress
- allow setting LNB LO frequency - allow setting LNB LO frequency
- save & load FFT history (allow big waterfall plot saved to file) - save & load FFT history (allow big waterfall plot saved to file)

View File

@ -11,6 +11,7 @@ from qspectrumanalyzer.plot import SpectrumPlotWidget, WaterfallPlotWidget
from qspectrumanalyzer.utils import color_to_str, str_to_color from qspectrumanalyzer.utils import color_to_str, str_to_color
from qspectrumanalyzer.ui_qspectrumanalyzer_settings import Ui_QSpectrumAnalyzerSettings from qspectrumanalyzer.ui_qspectrumanalyzer_settings import Ui_QSpectrumAnalyzerSettings
from qspectrumanalyzer.ui_qspectrumanalyzer_settings_help import Ui_QSpectrumAnalyzerSettingsHelp
from qspectrumanalyzer.ui_qspectrumanalyzer_smooth import Ui_QSpectrumAnalyzerSmooth from qspectrumanalyzer.ui_qspectrumanalyzer_smooth import Ui_QSpectrumAnalyzerSmooth
from qspectrumanalyzer.ui_qspectrumanalyzer_persistence import Ui_QSpectrumAnalyzerPersistence from qspectrumanalyzer.ui_qspectrumanalyzer_persistence import Ui_QSpectrumAnalyzerPersistence
from qspectrumanalyzer.ui_qspectrumanalyzer_colors import Ui_QSpectrumAnalyzerColors from qspectrumanalyzer.ui_qspectrumanalyzer_colors import Ui_QSpectrumAnalyzerColors
@ -27,6 +28,7 @@ class QSpectrumAnalyzerSettings(QtGui.QDialog, Ui_QSpectrumAnalyzerSettings):
# Initialize UI # Initialize UI
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
self.help_dialog = None
# Load settings # Load settings
settings = QtCore.QSettings() settings = QtCore.QSettings()
@ -40,15 +42,16 @@ class QSpectrumAnalyzerSettings(QtGui.QDialog, Ui_QSpectrumAnalyzerSettings):
except AttributeError: except AttributeError:
backend_module = backends.soapy_power backend_module = backends.soapy_power
self.paramsEdit.setText(settings.value("params", backend_module.Info.additional_params))
self.sampleRateSpinBox.setMinimum(backend_module.Info.sample_rate_min) self.sampleRateSpinBox.setMinimum(backend_module.Info.sample_rate_min)
self.sampleRateSpinBox.setMaximum(backend_module.Info.sample_rate_max) self.sampleRateSpinBox.setMaximum(backend_module.Info.sample_rate_max)
self.sampleRateSpinBox.setValue(settings.value("sample_rate", backend_module.Info.sample_rate, int)) self.sampleRateSpinBox.setValue(settings.value("sample_rate", backend_module.Info.sample_rate, int))
self.backendComboBox.blockSignals(True)
self.backendComboBox.clear() self.backendComboBox.clear()
for b in sorted(backends.__all__): for b in sorted(backends.__all__):
self.backendComboBox.addItem(b) self.backendComboBox.addItem(b)
self.backendComboBox.blockSignals(True)
i = self.backendComboBox.findText(backend) i = self.backendComboBox.findText(backend)
if i == -1: if i == -1:
self.backendComboBox.setCurrentIndex(0) self.backendComboBox.setCurrentIndex(0)
@ -63,6 +66,23 @@ class QSpectrumAnalyzerSettings(QtGui.QDialog, Ui_QSpectrumAnalyzerSettings):
if filename: if filename:
self.executableEdit.setText(filename) self.executableEdit.setText(filename)
@QtCore.pyqtSlot()
def on_helpButton_clicked(self):
"""Open help dialog when button is clicked"""
try:
backend_module = getattr(backends, self.backendComboBox.currentText())
except AttributeError:
backend_module = backends.soapy_power
self.help_dialog = QSpectrumAnalyzerSettingsHelp(
backend_module.Info.help(self.executableEdit.text()),
parent=self
)
self.help_dialog.show()
self.help_dialog.raise_()
self.help_dialog.activateWindow()
@QtCore.pyqtSlot(str) @QtCore.pyqtSlot(str)
def on_backendComboBox_currentIndexChanged(self, text): def on_backendComboBox_currentIndexChanged(self, text):
"""Change executable when backend is changed""" """Change executable when backend is changed"""
@ -74,6 +94,7 @@ class QSpectrumAnalyzerSettings(QtGui.QDialog, Ui_QSpectrumAnalyzerSettings):
except AttributeError: except AttributeError:
backend_module = backends.soapy_power backend_module = backends.soapy_power
self.paramsEdit.setText(backend_module.Info.additional_params)
self.sampleRateSpinBox.setMinimum(backend_module.Info.sample_rate_min) self.sampleRateSpinBox.setMinimum(backend_module.Info.sample_rate_min)
self.sampleRateSpinBox.setMaximum(backend_module.Info.sample_rate_max) self.sampleRateSpinBox.setMaximum(backend_module.Info.sample_rate_max)
self.sampleRateSpinBox.setValue(backend_module.Info.sample_rate) self.sampleRateSpinBox.setValue(backend_module.Info.sample_rate)
@ -84,11 +105,25 @@ class QSpectrumAnalyzerSettings(QtGui.QDialog, Ui_QSpectrumAnalyzerSettings):
settings.setValue("executable", self.executableEdit.text()) settings.setValue("executable", self.executableEdit.text())
settings.setValue("waterfall_history_size", self.waterfallHistorySizeSpinBox.value()) settings.setValue("waterfall_history_size", self.waterfallHistorySizeSpinBox.value())
settings.setValue("device", self.deviceEdit.text()) settings.setValue("device", self.deviceEdit.text())
settings.setValue("params", self.paramsEdit.text())
settings.setValue("sample_rate", self.sampleRateSpinBox.value()) settings.setValue("sample_rate", self.sampleRateSpinBox.value())
settings.setValue("backend", self.backendComboBox.currentText()) settings.setValue("backend", self.backendComboBox.currentText())
QtGui.QDialog.accept(self) QtGui.QDialog.accept(self)
class QSpectrumAnalyzerSettingsHelp(QtGui.QDialog, Ui_QSpectrumAnalyzerSettingsHelp):
"""QSpectrumAnalyzer settings help dialog"""
def __init__(self, text, parent=None):
# Initialize UI
super().__init__(parent)
self.setupUi(self)
monospace_font = QtGui.QFont('monospace')
monospace_font.setStyleHint(QtGui.QFont.Monospace)
self.helpTextEdit.setFont(monospace_font)
self.helpTextEdit.setPlainText(text)
class QSpectrumAnalyzerSmooth(QtGui.QDialog, Ui_QSpectrumAnalyzerSmooth): class QSpectrumAnalyzerSmooth(QtGui.QDialog, Ui_QSpectrumAnalyzerSmooth):
"""QSpectrumAnalyzer spectrum smoothing dialog""" """QSpectrumAnalyzer spectrum smoothing dialog"""
def __init__(self, parent=None): def __init__(self, parent=None):
@ -185,6 +220,7 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
self.prev_data_timestamp = None self.prev_data_timestamp = None
self.data_storage = None self.data_storage = None
self.power_thread = None self.power_thread = None
self.backend = None
self.setup_power_thread() self.setup_power_thread()
self.update_buttons() self.update_buttons()
@ -213,27 +249,29 @@ class QSpectrumAnalyzerMainWindow(QtGui.QMainWindow, Ui_QSpectrumAnalyzerMainWin
except AttributeError: except AttributeError:
backend_module = backends.soapy_power backend_module = backends.soapy_power
self.gainSpinBox.setMinimum(backend_module.Info.gain_min) if not self.backend or backend != self.backend:
self.gainSpinBox.setMaximum(backend_module.Info.gain_max) self.backend = backend
self.gainSpinBox.setValue(backend_module.Info.gain) self.gainSpinBox.setMinimum(backend_module.Info.gain_min)
self.startFreqSpinBox.setMinimum(backend_module.Info.start_freq_min) self.gainSpinBox.setMaximum(backend_module.Info.gain_max)
self.startFreqSpinBox.setMaximum(backend_module.Info.start_freq_max) self.gainSpinBox.setValue(backend_module.Info.gain)
self.startFreqSpinBox.setValue(backend_module.Info.start_freq) self.startFreqSpinBox.setMinimum(backend_module.Info.start_freq_min)
self.stopFreqSpinBox.setMinimum(backend_module.Info.stop_freq_min) self.startFreqSpinBox.setMaximum(backend_module.Info.start_freq_max)
self.stopFreqSpinBox.setMaximum(backend_module.Info.stop_freq_max) self.startFreqSpinBox.setValue(backend_module.Info.start_freq)
self.stopFreqSpinBox.setValue(backend_module.Info.stop_freq) self.stopFreqSpinBox.setMinimum(backend_module.Info.stop_freq_min)
self.binSizeSpinBox.setMinimum(backend_module.Info.bin_size_min) self.stopFreqSpinBox.setMaximum(backend_module.Info.stop_freq_max)
self.binSizeSpinBox.setMaximum(backend_module.Info.bin_size_max) self.stopFreqSpinBox.setValue(backend_module.Info.stop_freq)
self.binSizeSpinBox.setValue(backend_module.Info.bin_size) self.binSizeSpinBox.setMinimum(backend_module.Info.bin_size_min)
self.intervalSpinBox.setMinimum(backend_module.Info.interval_min) self.binSizeSpinBox.setMaximum(backend_module.Info.bin_size_max)
self.intervalSpinBox.setMaximum(backend_module.Info.interval_max) self.binSizeSpinBox.setValue(backend_module.Info.bin_size)
self.intervalSpinBox.setValue(backend_module.Info.interval) self.intervalSpinBox.setMinimum(backend_module.Info.interval_min)
self.ppmSpinBox.setMinimum(backend_module.Info.ppm_min) self.intervalSpinBox.setMaximum(backend_module.Info.interval_max)
self.ppmSpinBox.setMaximum(backend_module.Info.ppm_max) self.intervalSpinBox.setValue(backend_module.Info.interval)
self.ppmSpinBox.setValue(backend_module.Info.ppm) self.ppmSpinBox.setMinimum(backend_module.Info.ppm_min)
self.cropSpinBox.setMinimum(backend_module.Info.crop_min) self.ppmSpinBox.setMaximum(backend_module.Info.ppm_max)
self.cropSpinBox.setMaximum(backend_module.Info.crop_max) self.ppmSpinBox.setValue(backend_module.Info.ppm)
self.cropSpinBox.setValue(backend_module.Info.crop) self.cropSpinBox.setMinimum(backend_module.Info.crop_min)
self.cropSpinBox.setMaximum(backend_module.Info.crop_max)
self.cropSpinBox.setValue(backend_module.Info.crop)
self.power_thread = backend_module.PowerThread(self.data_storage) self.power_thread = backend_module.PowerThread(self.data_storage)
self.power_thread.powerThreadStarted.connect(self.update_buttons) self.power_thread.powerThreadStarted.connect(self.update_buttons)

View File

@ -1,4 +1,4 @@
import os, glob import os, glob, subprocess
from PyQt4 import QtCore from PyQt4 import QtCore
@ -6,16 +6,16 @@ from PyQt4 import QtCore
class BaseInfo: class BaseInfo:
"""Default device metadata""" """Default device metadata"""
sample_rate_min = 0 sample_rate_min = 0
sample_rate_max = 61440000 sample_rate_max = 3200000
sample_rate = 2560000 sample_rate = 2560000
gain_min = -1 gain_min = -1
gain_max = 49 gain_max = 49
gain = -1 gain = 37
start_freq_min = 24 start_freq_min = 24
start_freq_max = 1766 start_freq_max = 2200
start_freq = 87 start_freq = 87
stop_freq_min = 24 stop_freq_min = 24
stop_freq_max = 1766 stop_freq_max = 2200
stop_freq = 108 stop_freq = 108
bin_size_min = 0 bin_size_min = 0
bin_size_max = 2800 bin_size_max = 2800
@ -29,6 +29,18 @@ class BaseInfo:
crop_min = 0 crop_min = 0
crop_max = 99 crop_max = 99
crop = 0 crop = 0
additional_params = ''
@classmethod
def help(cls, executable):
try:
p = subprocess.run([executable, '-h'], universal_newlines=True,
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
env=dict(os.environ, COLUMNS='125'))
text = p.stdout
except OSError:
text = '{} executable not found!'.format(executable)
return text
class BasePowerThread(QtCore.QThread): class BasePowerThread(QtCore.QThread):

View File

@ -1,8 +1,7 @@
import subprocess, pprint import subprocess, pprint, struct, shlex
import numpy as np import numpy as np
from PyQt4 import QtCore from PyQt4 import QtCore
import struct
from qspectrumanalyzer.backends import BaseInfo, BasePowerThread from qspectrumanalyzer.backends import BaseInfo, BasePowerThread
@ -102,6 +101,10 @@ class PowerThread(BasePowerThread):
if self.params["single_shot"]: if self.params["single_shot"]:
cmdline.append("-1") cmdline.append("-1")
additional_params = settings.value("params", Info.additional_params)
if additional_params:
cmdline.extend(shlex.split(additional_params))
self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
universal_newlines=False) universal_newlines=False)
@ -138,6 +141,10 @@ class PowerThread(BasePowerThread):
buf = self.process.stdout.read(record_length) buf = self.process.stdout.read(record_length)
if buf: if buf:
self.parse_output(buf) self.parse_output(buf)
else:
break
else:
break
self.process_stop() self.process_stop()
self.alive = False self.alive = False

View File

@ -1,4 +1,4 @@
import subprocess, pprint import subprocess, pprint, shlex
import numpy as np import numpy as np
from PyQt4 import QtCore from PyQt4 import QtCore
@ -57,6 +57,10 @@ class PowerThread(BasePowerThread):
if self.params["single_shot"]: if self.params["single_shot"]:
cmdline.append("-1") cmdline.append("-1")
additional_params = settings.value("params", Info.additional_params)
if additional_params:
cmdline.extend(shlex.split(additional_params))
self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
universal_newlines=True) universal_newlines=True)

View File

@ -1,4 +1,4 @@
import subprocess, math, pprint import subprocess, math, pprint, shlex
from PyQt4 import QtCore from PyQt4 import QtCore
@ -84,6 +84,10 @@ class PowerThread(BasePowerThread):
if not self.params["single_shot"]: if not self.params["single_shot"]:
cmdline.append("-c") cmdline.append("-c")
additional_params = settings.value("params", Info.additional_params)
if additional_params:
cmdline.extend(shlex.split(additional_params))
self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
universal_newlines=True) universal_newlines=True)

View File

@ -1,4 +1,4 @@
import subprocess, pprint import subprocess, pprint, shlex
import numpy as np import numpy as np
from PyQt4 import QtCore from PyQt4 import QtCore
@ -8,7 +8,12 @@ from qspectrumanalyzer.backends import BaseInfo, BasePowerThread
class Info(BaseInfo): class Info(BaseInfo):
"""rx_power device metadata""" """rx_power device metadata"""
pass sample_rate_min = 0
sample_rate_max = 61440000
start_freq_min = 0
start_freq_max = 6000
stop_freq_min = 0
stop_freq_max = 6000
class PowerThread(BasePowerThread): class PowerThread(BasePowerThread):
@ -55,6 +60,10 @@ class PowerThread(BasePowerThread):
if self.params["single_shot"]: if self.params["single_shot"]:
cmdline.append("-1") cmdline.append("-1")
additional_params = settings.value("params", Info.additional_params)
if additional_params:
cmdline.extend(shlex.split(additional_params))
self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
universal_newlines=True) universal_newlines=True)

View File

@ -1,19 +1,27 @@
import subprocess, pprint, re import subprocess, pprint, sys, shlex
import numpy as np
from PyQt4 import QtCore from PyQt4 import QtCore
from qspectrumanalyzer.backends import BaseInfo, BasePowerThread from qspectrumanalyzer.backends import BaseInfo, BasePowerThread
from soapypower.writer import SoapyPowerBinFormat
formatter = SoapyPowerBinFormat()
class Info(BaseInfo): class Info(BaseInfo):
"""soapy_power device metadata""" """soapy_power device metadata"""
pass sample_rate_min = 0
sample_rate_max = 61440000
start_freq_min = 0
start_freq_max = 6000
stop_freq_min = 0
stop_freq_max = 6000
additional_params = '--even --fft-window boxcar --remove-dc'
class PowerThread(BasePowerThread): class PowerThread(BasePowerThread):
"""Thread which runs soapy_power process""" """Thread which runs soapy_power process"""
re_two_floats = re.compile(r'^[-+\d.eE]+\s+[-+\d.eE]+$')
def setup(self, start_freq, stop_freq, bin_size, interval=10.0, gain=-1, def setup(self, start_freq, stop_freq, bin_size, interval=10.0, gain=-1,
ppm=0, crop=0, single_shot=False, device="", sample_rate=2560000): ppm=0, crop=0, single_shot=False, device="", sample_rate=2560000):
"""Setup soapy_power params""" """Setup soapy_power params"""
@ -31,10 +39,7 @@ class PowerThread(BasePowerThread):
"single_shot": single_shot "single_shot": single_shot
} }
self.databuffer = {"timestamp": [], "x": [], "y": []} self.databuffer = {"timestamp": [], "x": [], "y": []}
self.databuffer_hop = {"timestamp": [], "x": [], "y": []} self.min_freq = 0
self.hop = 0
self.run = 0
self.prev_line = ""
print("soapy_power params:") print("soapy_power params:")
pprint.pprint(self.params) pprint.pprint(self.params)
@ -53,6 +58,7 @@ class PowerThread(BasePowerThread):
"-d", "{}".format(self.params["device"]), "-d", "{}".format(self.params["device"]),
"-r", "{}".format(self.params["sample_rate"]), "-r", "{}".format(self.params["sample_rate"]),
"-p", "{}".format(self.params["ppm"]), "-p", "{}".format(self.params["ppm"]),
"-F", "soapy_power_bin",
] ]
if self.params["gain"] >= 0: if self.params["gain"] >= 0:
@ -62,50 +68,59 @@ class PowerThread(BasePowerThread):
if not self.params["single_shot"]: if not self.params["single_shot"]:
cmdline.append("-c") cmdline.append("-c")
additional_params = settings.value("params", Info.additional_params)
if additional_params:
cmdline.extend(shlex.split(additional_params))
self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, self.process = subprocess.Popen(cmdline, stdout=subprocess.PIPE,
universal_newlines=True) universal_newlines=False)
def parse_output(self, line): def parse_output(self, data):
"""Parse one line of output from soapy_power""" """Parse data from soapy_power"""
line = line.strip() header, y_axis = data
# One empty line => new hop timestamp = header.timestamp
if not line and self.prev_line: start_freq = header.start
self.hop += 1 stop_freq = header.stop
print(' => HOP:', self.hop) step = header.step
self.databuffer["x"].extend(self.databuffer_hop["x"]) samples = header.samples
self.databuffer["y"].extend(self.databuffer_hop["y"])
self.databuffer_hop = {"timestamp": [], "x": [], "y": []}
# Two empty lines => new set x_axis = np.arange(start_freq, stop_freq, step)
elif not line and not self.prev_line: if len(x_axis) != len(y_axis):
self.hop = 0 print("ERROR: len(x_axis) != len(y_axis)")
self.run += 1
print(' * RUN:', self.run) if not self.min_freq:
self.min_freq = start_freq
if start_freq == self.min_freq:
self.databuffer = {"timestamp": timestamp,
"x": list(x_axis),
"y": list(y_axis)}
else:
self.databuffer["x"].extend(x_axis)
self.databuffer["y"].extend(y_axis)
if stop_freq > (self.params["stop_freq"] * 1e6) - step:
self.data_storage.update(self.databuffer) self.data_storage.update(self.databuffer)
self.databuffer = {"timestamp": [], "x": [], "y": []}
# Get timestamp for new hop and set def run(self):
elif line.startswith("# Acquisition start:"): """soapy_power thread main loop"""
timestamp = line.split(":", 1)[1].strip() self.process_start()
if not self.databuffer_hop["timestamp"]: self.alive = True
self.databuffer_hop["timestamp"] = timestamp self.powerThreadStarted.emit()
if not self.databuffer["timestamp"]:
self.databuffer["timestamp"] = timestamp
# Skip other comments while self.alive:
elif line.startswith("#"):
pass
# Parse frequency and power
elif self.re_two_floats.match(line):
try: try:
freq, power = line.split() data = formatter.read(self.process.stdout)
except ValueError: except ValueError as e:
return print(e, file=sys.stderr)
break
freq, power = float(freq), float(power) if data:
self.databuffer_hop["x"].append(freq) self.parse_output(data)
self.databuffer_hop["y"].append(power) else:
break
self.prev_line = line self.process_stop()
self.alive = False
self.powerThreadStopped.emit()

View File

@ -81,22 +81,22 @@
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../__main__.py" line="337"/> <location filename="../__main__.py" line="383"/>
<source>Frequency hops: {} | Sweep time: {:.2f} s | FPS: {:.2f}</source> <source>Frequency hops: {} | Sweep time: {:.2f} s | FPS: {:.2f}</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../__main__.py" line="337"/> <location filename="../__main__.py" line="383"/>
<source>N/A</source> <source>N/A</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../__main__.py" line="504"/> <location filename="../__main__.py" line="550"/>
<source>About - QSpectrumAnalyzer</source> <source>About - QSpectrumAnalyzer</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../__main__.py" line="504"/> <location filename="../__main__.py" line="550"/>
<source>QSpectrumAnalyzer {}</source> <source>QSpectrumAnalyzer {}</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@ -252,70 +252,88 @@
<context> <context>
<name>QSpectrumAnalyzerSettings</name> <name>QSpectrumAnalyzerSettings</name>
<message> <message>
<location filename="../__main__.py" line="50"/> <location filename="../__main__.py" line="65"/>
<source>Select executable - QSpectrumAnalyzer</source> <source>Select executable - QSpectrumAnalyzer</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="107"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="122"/>
<source>Settings - QSpectrumAnalyzer</source> <source>Settings - QSpectrumAnalyzer</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="108"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="123"/>
<source>&amp;Backend:</source> <source>&amp;Backend:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="115"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="130"/>
<source>soapy_power</source> <source>soapy_power</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="110"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="125"/>
<source>rx_power</source> <source>rx_power</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="111"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="126"/>
<source>rtl_power_fftw</source> <source>rtl_power_fftw</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="112"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="127"/>
<source>rtl_power</source> <source>rtl_power</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="113"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="128"/>
<source>hackrf_sweep</source> <source>hackrf_sweep</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="114"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="129"/>
<source>E&amp;xecutable:</source> <source>E&amp;xecutable:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="116"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="131"/>
<source>...</source> <source>...</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="117"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="133"/>
<source>Device:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="118"/>
<source>Sa&amp;mple rate:</source> <source>Sa&amp;mple rate:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="119"/> <location filename="../ui_qspectrumanalyzer_settings.py" line="134"/>
<source>&amp;Waterfall history size:</source> <source>&amp;Waterfall history size:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="132"/>
<source>&amp;Device:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="135"/>
<source>Additional &amp;parameters:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui_qspectrumanalyzer_settings.py" line="136"/>
<source>?</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QSpectrumAnalyzerSettingsHelp</name>
<message>
<location filename="../ui_qspectrumanalyzer_settings_help.py" line="49"/>
<source>Help - QSpectrumAnalyzer</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>QSpectrumAnalyzerSmooth</name> <name>QSpectrumAnalyzerSmooth</name>

View File

@ -42,6 +42,9 @@ class SpectrumPlotWidget:
self.plot.setLimits(xMin=0) self.plot.setLimits(xMin=0)
self.plot.showButtons() self.plot.showButtons()
#self.plot.setDownsampling(mode="peak")
#self.plot.setClipToView(True)
self.create_persistence_curves() self.create_persistence_curves()
self.create_average_curve() self.create_average_curve()
self.create_peak_hold_min_curve() self.create_peak_hold_min_curve()
@ -264,6 +267,7 @@ class WaterfallPlotWidget:
self.plot.setLimits(xMin=0, yMax=0) self.plot.setLimits(xMin=0, yMax=0)
self.plot.showButtons() self.plot.showButtons()
#self.plot.setAspectLocked(True) #self.plot.setAspectLocked(True)
#self.plot.setDownsampling(mode="peak") #self.plot.setDownsampling(mode="peak")
#self.plot.setClipToView(True) #self.plot.setClipToView(True)

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>420</width> <width>600</width>
<height>255</height> <height>310</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -86,7 +86,7 @@
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label_5"> <widget class="QLabel" name="label_5">
<property name="text"> <property name="text">
<string>Device:</string> <string>&amp;Device:</string>
</property> </property>
<property name="buddy"> <property name="buddy">
<cstring>deviceEdit</cstring> <cstring>deviceEdit</cstring>
@ -96,7 +96,7 @@
<item row="2" column="1"> <item row="2" column="1">
<widget class="QLineEdit" name="deviceEdit"/> <widget class="QLineEdit" name="deviceEdit"/>
</item> </item>
<item row="3" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4">
<property name="text"> <property name="text">
<string>Sa&amp;mple rate:</string> <string>Sa&amp;mple rate:</string>
@ -106,7 +106,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="4" column="1">
<widget class="QSpinBox" name="sampleRateSpinBox"> <widget class="QSpinBox" name="sampleRateSpinBox">
<property name="minimum"> <property name="minimum">
<number>0</number> <number>0</number>
@ -122,7 +122,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0"> <item row="5" column="0">
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>&amp;Waterfall history size:</string> <string>&amp;Waterfall history size:</string>
@ -132,7 +132,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="1"> <item row="5" column="1">
<widget class="QSpinBox" name="waterfallHistorySizeSpinBox"> <widget class="QSpinBox" name="waterfallHistorySizeSpinBox">
<property name="minimum"> <property name="minimum">
<number>1</number> <number>1</number>
@ -145,6 +145,30 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Additional &amp;parameters:</string>
</property>
<property name="buddy">
<cstring>paramsEdit</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLineEdit" name="paramsEdit"/>
</item>
<item>
<widget class="QToolButton" name="helpButton">
<property name="text">
<string>?</string>
</property>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -177,6 +201,8 @@
<tabstop>executableEdit</tabstop> <tabstop>executableEdit</tabstop>
<tabstop>executableButton</tabstop> <tabstop>executableButton</tabstop>
<tabstop>deviceEdit</tabstop> <tabstop>deviceEdit</tabstop>
<tabstop>paramsEdit</tabstop>
<tabstop>helpButton</tabstop>
<tabstop>sampleRateSpinBox</tabstop> <tabstop>sampleRateSpinBox</tabstop>
<tabstop>waterfallHistorySizeSpinBox</tabstop> <tabstop>waterfallHistorySizeSpinBox</tabstop>
<tabstop>buttonBox</tabstop> <tabstop>buttonBox</tabstop>
@ -190,8 +216,8 @@
<slot>accept()</slot> <slot>accept()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>242</x> <x>248</x>
<y>248</y> <y>303</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>157</x> <x>157</x>
@ -206,8 +232,8 @@
<slot>reject()</slot> <slot>reject()</slot>
<hints> <hints>
<hint type="sourcelabel"> <hint type="sourcelabel">
<x>310</x> <x>316</x>
<y>248</y> <y>303</y>
</hint> </hint>
<hint type="destinationlabel"> <hint type="destinationlabel">
<x>286</x> <x>286</x>

View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QSpectrumAnalyzerSettingsHelp</class>
<widget class="QDialog" name="QSpectrumAnalyzerSettingsHelp">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1200</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>Help - QSpectrumAnalyzer</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPlainTextEdit" name="helpTextEdit">
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<tabstops>
<tabstop>helpTextEdit</tabstop>
<tabstop>buttonBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>QSpectrumAnalyzerSettingsHelp</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>224</x>
<y>672</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>QSpectrumAnalyzerSettingsHelp</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>292</x>
<y>678</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -25,7 +25,7 @@ except AttributeError:
class Ui_QSpectrumAnalyzerSettings(object): class Ui_QSpectrumAnalyzerSettings(object):
def setupUi(self, QSpectrumAnalyzerSettings): def setupUi(self, QSpectrumAnalyzerSettings):
QSpectrumAnalyzerSettings.setObjectName(_fromUtf8("QSpectrumAnalyzerSettings")) QSpectrumAnalyzerSettings.setObjectName(_fromUtf8("QSpectrumAnalyzerSettings"))
QSpectrumAnalyzerSettings.resize(420, 255) QSpectrumAnalyzerSettings.resize(600, 310)
self.verticalLayout = QtGui.QVBoxLayout(QSpectrumAnalyzerSettings) self.verticalLayout = QtGui.QVBoxLayout(QSpectrumAnalyzerSettings)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout")) self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.formLayout = QtGui.QFormLayout() self.formLayout = QtGui.QFormLayout()
@ -61,23 +61,35 @@ class Ui_QSpectrumAnalyzerSettings(object):
self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.deviceEdit) self.formLayout.setWidget(2, QtGui.QFormLayout.FieldRole, self.deviceEdit)
self.label_4 = QtGui.QLabel(QSpectrumAnalyzerSettings) self.label_4 = QtGui.QLabel(QSpectrumAnalyzerSettings)
self.label_4.setObjectName(_fromUtf8("label_4")) self.label_4.setObjectName(_fromUtf8("label_4"))
self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.label_4) self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.label_4)
self.sampleRateSpinBox = QtGui.QSpinBox(QSpectrumAnalyzerSettings) self.sampleRateSpinBox = QtGui.QSpinBox(QSpectrumAnalyzerSettings)
self.sampleRateSpinBox.setMinimum(0) self.sampleRateSpinBox.setMinimum(0)
self.sampleRateSpinBox.setMaximum(25000000) self.sampleRateSpinBox.setMaximum(25000000)
self.sampleRateSpinBox.setSingleStep(10000) self.sampleRateSpinBox.setSingleStep(10000)
self.sampleRateSpinBox.setProperty("value", 2560000) self.sampleRateSpinBox.setProperty("value", 2560000)
self.sampleRateSpinBox.setObjectName(_fromUtf8("sampleRateSpinBox")) self.sampleRateSpinBox.setObjectName(_fromUtf8("sampleRateSpinBox"))
self.formLayout.setWidget(3, QtGui.QFormLayout.FieldRole, self.sampleRateSpinBox) self.formLayout.setWidget(4, QtGui.QFormLayout.FieldRole, self.sampleRateSpinBox)
self.label_2 = QtGui.QLabel(QSpectrumAnalyzerSettings) self.label_2 = QtGui.QLabel(QSpectrumAnalyzerSettings)
self.label_2.setObjectName(_fromUtf8("label_2")) self.label_2.setObjectName(_fromUtf8("label_2"))
self.formLayout.setWidget(4, QtGui.QFormLayout.LabelRole, self.label_2) self.formLayout.setWidget(5, QtGui.QFormLayout.LabelRole, self.label_2)
self.waterfallHistorySizeSpinBox = QtGui.QSpinBox(QSpectrumAnalyzerSettings) self.waterfallHistorySizeSpinBox = QtGui.QSpinBox(QSpectrumAnalyzerSettings)
self.waterfallHistorySizeSpinBox.setMinimum(1) self.waterfallHistorySizeSpinBox.setMinimum(1)
self.waterfallHistorySizeSpinBox.setMaximum(10000000) self.waterfallHistorySizeSpinBox.setMaximum(10000000)
self.waterfallHistorySizeSpinBox.setProperty("value", 100) self.waterfallHistorySizeSpinBox.setProperty("value", 100)
self.waterfallHistorySizeSpinBox.setObjectName(_fromUtf8("waterfallHistorySizeSpinBox")) self.waterfallHistorySizeSpinBox.setObjectName(_fromUtf8("waterfallHistorySizeSpinBox"))
self.formLayout.setWidget(4, QtGui.QFormLayout.FieldRole, self.waterfallHistorySizeSpinBox) self.formLayout.setWidget(5, QtGui.QFormLayout.FieldRole, self.waterfallHistorySizeSpinBox)
self.label_6 = QtGui.QLabel(QSpectrumAnalyzerSettings)
self.label_6.setObjectName(_fromUtf8("label_6"))
self.formLayout.setWidget(3, QtGui.QFormLayout.LabelRole, self.label_6)
self.horizontalLayout_3 = QtGui.QHBoxLayout()
self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3"))
self.paramsEdit = QtGui.QLineEdit(QSpectrumAnalyzerSettings)
self.paramsEdit.setObjectName(_fromUtf8("paramsEdit"))
self.horizontalLayout_3.addWidget(self.paramsEdit)
self.helpButton = QtGui.QToolButton(QSpectrumAnalyzerSettings)
self.helpButton.setObjectName(_fromUtf8("helpButton"))
self.horizontalLayout_3.addWidget(self.helpButton)
self.formLayout.setLayout(3, QtGui.QFormLayout.FieldRole, self.horizontalLayout_3)
self.verticalLayout.addLayout(self.formLayout) self.verticalLayout.addLayout(self.formLayout)
spacerItem = QtGui.QSpacerItem(20, 21, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding) spacerItem = QtGui.QSpacerItem(20, 21, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
self.verticalLayout.addItem(spacerItem) self.verticalLayout.addItem(spacerItem)
@ -91,6 +103,7 @@ class Ui_QSpectrumAnalyzerSettings(object):
self.label_5.setBuddy(self.deviceEdit) self.label_5.setBuddy(self.deviceEdit)
self.label_4.setBuddy(self.sampleRateSpinBox) self.label_4.setBuddy(self.sampleRateSpinBox)
self.label_2.setBuddy(self.waterfallHistorySizeSpinBox) self.label_2.setBuddy(self.waterfallHistorySizeSpinBox)
self.label_6.setBuddy(self.paramsEdit)
self.retranslateUi(QSpectrumAnalyzerSettings) self.retranslateUi(QSpectrumAnalyzerSettings)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), QSpectrumAnalyzerSettings.accept) QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), QSpectrumAnalyzerSettings.accept)
@ -99,7 +112,9 @@ class Ui_QSpectrumAnalyzerSettings(object):
QSpectrumAnalyzerSettings.setTabOrder(self.backendComboBox, self.executableEdit) QSpectrumAnalyzerSettings.setTabOrder(self.backendComboBox, self.executableEdit)
QSpectrumAnalyzerSettings.setTabOrder(self.executableEdit, self.executableButton) QSpectrumAnalyzerSettings.setTabOrder(self.executableEdit, self.executableButton)
QSpectrumAnalyzerSettings.setTabOrder(self.executableButton, self.deviceEdit) QSpectrumAnalyzerSettings.setTabOrder(self.executableButton, self.deviceEdit)
QSpectrumAnalyzerSettings.setTabOrder(self.deviceEdit, self.sampleRateSpinBox) QSpectrumAnalyzerSettings.setTabOrder(self.deviceEdit, self.paramsEdit)
QSpectrumAnalyzerSettings.setTabOrder(self.paramsEdit, self.helpButton)
QSpectrumAnalyzerSettings.setTabOrder(self.helpButton, self.sampleRateSpinBox)
QSpectrumAnalyzerSettings.setTabOrder(self.sampleRateSpinBox, self.waterfallHistorySizeSpinBox) QSpectrumAnalyzerSettings.setTabOrder(self.sampleRateSpinBox, self.waterfallHistorySizeSpinBox)
QSpectrumAnalyzerSettings.setTabOrder(self.waterfallHistorySizeSpinBox, self.buttonBox) QSpectrumAnalyzerSettings.setTabOrder(self.waterfallHistorySizeSpinBox, self.buttonBox)
@ -114,7 +129,9 @@ class Ui_QSpectrumAnalyzerSettings(object):
self.label.setText(_translate("QSpectrumAnalyzerSettings", "E&xecutable:", None)) self.label.setText(_translate("QSpectrumAnalyzerSettings", "E&xecutable:", None))
self.executableEdit.setText(_translate("QSpectrumAnalyzerSettings", "soapy_power", None)) self.executableEdit.setText(_translate("QSpectrumAnalyzerSettings", "soapy_power", None))
self.executableButton.setText(_translate("QSpectrumAnalyzerSettings", "...", None)) self.executableButton.setText(_translate("QSpectrumAnalyzerSettings", "...", None))
self.label_5.setText(_translate("QSpectrumAnalyzerSettings", "Device:", None)) self.label_5.setText(_translate("QSpectrumAnalyzerSettings", "&Device:", None))
self.label_4.setText(_translate("QSpectrumAnalyzerSettings", "Sa&mple rate:", None)) self.label_4.setText(_translate("QSpectrumAnalyzerSettings", "Sa&mple rate:", None))
self.label_2.setText(_translate("QSpectrumAnalyzerSettings", "&Waterfall history size:", None)) self.label_2.setText(_translate("QSpectrumAnalyzerSettings", "&Waterfall history size:", None))
self.label_6.setText(_translate("QSpectrumAnalyzerSettings", "Additional &parameters:", None))
self.helpButton.setText(_translate("QSpectrumAnalyzerSettings", "?", None))

View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'qspectrumanalyzer/qspectrumanalyzer_settings_help.ui'
#
# Created by: PyQt4 UI code generator 4.12
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_QSpectrumAnalyzerSettingsHelp(object):
def setupUi(self, QSpectrumAnalyzerSettingsHelp):
QSpectrumAnalyzerSettingsHelp.setObjectName(_fromUtf8("QSpectrumAnalyzerSettingsHelp"))
QSpectrumAnalyzerSettingsHelp.resize(1200, 700)
self.verticalLayout = QtGui.QVBoxLayout(QSpectrumAnalyzerSettingsHelp)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.helpTextEdit = QtGui.QPlainTextEdit(QSpectrumAnalyzerSettingsHelp)
self.helpTextEdit.setUndoRedoEnabled(False)
self.helpTextEdit.setTextInteractionFlags(QtCore.Qt.TextSelectableByKeyboard|QtCore.Qt.TextSelectableByMouse)
self.helpTextEdit.setObjectName(_fromUtf8("helpTextEdit"))
self.verticalLayout.addWidget(self.helpTextEdit)
self.buttonBox = QtGui.QDialogButtonBox(QSpectrumAnalyzerSettingsHelp)
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtGui.QDialogButtonBox.Close)
self.buttonBox.setObjectName(_fromUtf8("buttonBox"))
self.verticalLayout.addWidget(self.buttonBox)
self.retranslateUi(QSpectrumAnalyzerSettingsHelp)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("accepted()")), QSpectrumAnalyzerSettingsHelp.accept)
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("rejected()")), QSpectrumAnalyzerSettingsHelp.reject)
QtCore.QMetaObject.connectSlotsByName(QSpectrumAnalyzerSettingsHelp)
QSpectrumAnalyzerSettingsHelp.setTabOrder(self.helpTextEdit, self.buttonBox)
def retranslateUi(self, QSpectrumAnalyzerSettingsHelp):
QSpectrumAnalyzerSettingsHelp.setWindowTitle(_translate("QSpectrumAnalyzerSettingsHelp", "Help - QSpectrumAnalyzer", None))

View File

@ -1 +1 @@
__version__ = "1.4.0" __version__ = "1.5.0"

View File

@ -6,7 +6,8 @@ from qspectrumanalyzer.version import __version__
setup( setup(
name="QSpectrumAnalyzer", name="QSpectrumAnalyzer",
version=__version__, version=__version__,
description="Spectrum analyzer for RTL-SDR (GUI for rtl_power based on PyQtGraph)", description="Spectrum analyzer for multiple SDR platforms (PyQtGraph based GUI for soapy_power, rx_power, rtl_power, hackrf_sweep and other backends)",
long_description=open('README.rst').read(),
author="Michal Krenek (Mikos)", author="Michal Krenek (Mikos)",
author_email="m.krenek@gmail.com", author_email="m.krenek@gmail.com",
url="https://github.com/xmikos/qspectrumanalyzer", url="https://github.com/xmikos/qspectrumanalyzer",
@ -29,6 +30,7 @@ setup(
], ],
}, },
install_requires=[ install_requires=[
"soapy_power",
"pyqtgraph" "pyqtgraph"
], ],
classifiers=[ classifiers=[