2020-02-03 11:58:31 +00:00
|
|
|
#!/usr/bin/env python3
|
2013-04-28 10:41:35 +00:00
|
|
|
#
|
|
|
|
# Copyright 2012 Free Software Foundation, Inc.
|
|
|
|
#
|
|
|
|
# This file is part of GNU Radio
|
|
|
|
#
|
|
|
|
# GNU Radio is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation; either version 3, or (at your option)
|
|
|
|
# any later version.
|
|
|
|
#
|
|
|
|
# GNU Radio is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with GNU Radio; see the file COPYING. If not, write to
|
|
|
|
# the Free Software Foundation, Inc., 51 Franklin Street,
|
|
|
|
# Boston, MA 02110-1301, USA.
|
|
|
|
#
|
|
|
|
|
|
|
|
import osmosdr
|
2014-03-11 09:46:18 +00:00
|
|
|
from gnuradio import blocks
|
2020-02-03 11:58:31 +00:00
|
|
|
from gnuradio import gr
|
2013-04-28 10:41:35 +00:00
|
|
|
from gnuradio import eng_notation
|
2020-12-30 15:16:01 +00:00
|
|
|
from gnuradio.fft import window
|
2013-04-28 10:41:35 +00:00
|
|
|
from gnuradio.eng_option import eng_option
|
|
|
|
from optparse import OptionParser
|
2020-02-03 11:58:31 +00:00
|
|
|
from functools import partial
|
2013-04-28 10:41:35 +00:00
|
|
|
|
|
|
|
import sys
|
2020-02-03 11:58:31 +00:00
|
|
|
import signal
|
2014-03-11 09:46:18 +00:00
|
|
|
import time
|
|
|
|
import datetime
|
2013-04-28 10:41:35 +00:00
|
|
|
|
|
|
|
try:
|
2020-02-03 11:58:31 +00:00
|
|
|
from PyQt5 import Qt
|
|
|
|
from gnuradio import qtgui
|
|
|
|
import sip
|
|
|
|
from gnuradio.qtgui import Range, RangeWidget
|
2013-04-28 10:41:35 +00:00
|
|
|
except ImportError:
|
2020-02-03 11:58:31 +00:00
|
|
|
sys.stderr.write("Error importing GNU Radio's Qtgui.\n")
|
2013-04-28 10:41:35 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
class CallEvent(Qt.QEvent):
|
|
|
|
"""An event containing a request for a function call."""
|
|
|
|
EVENT_TYPE = Qt.QEvent.Type(Qt.QEvent.registerEventType())
|
|
|
|
|
|
|
|
def __init__(self, fn, *args, **kwargs):
|
|
|
|
Qt.QEvent.__init__(self, self.EVENT_TYPE)
|
|
|
|
self.fn = fn
|
|
|
|
self.args = args
|
|
|
|
self.kwargs = kwargs
|
|
|
|
|
|
|
|
|
|
|
|
class freq_recv(gr.sync_block, Qt.QObject):
|
|
|
|
def __init__(self, callback):
|
|
|
|
gr.sync_block.__init__(self, name="freq_recv", in_sig=None, out_sig=None)
|
|
|
|
Qt.QObject.__init__(self)
|
|
|
|
|
|
|
|
self.set_freq=callback
|
|
|
|
|
|
|
|
# Advertise 'msg' port
|
|
|
|
self.message_port_register_in(gr.pmt.intern('msg'))
|
|
|
|
self.set_msg_handler(gr.pmt.intern('msg'), self.handle_msg)
|
|
|
|
|
|
|
|
def handle_msg(self, msg_pmt):
|
|
|
|
# Unpack message & call set_freq on main thread
|
|
|
|
meta = gr.pmt.to_python(gr.pmt.car(msg_pmt))
|
|
|
|
msg = gr.pmt.cdr(msg_pmt)
|
|
|
|
if meta=="freq":
|
|
|
|
freq = gr.pmt.to_double(msg)
|
|
|
|
Qt.QCoreApplication.postEvent(self, CallEvent(self.set_freq, freq))
|
|
|
|
|
|
|
|
def event(self, event):
|
|
|
|
event.accept()
|
|
|
|
result = event.fn(*event.args, **event.kwargs)
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
class app_top_block(gr.top_block, Qt.QMainWindow):
|
|
|
|
def __init__(self, argv, title):
|
|
|
|
gr.top_block.__init__(self, title)
|
|
|
|
Qt.QMainWindow.__init__(self)
|
|
|
|
self.setWindowTitle(title)
|
2013-04-28 10:41:35 +00:00
|
|
|
|
|
|
|
parser = OptionParser(option_class=eng_option)
|
|
|
|
parser.add_option("-a", "--args", type="string", default="",
|
2013-05-09 11:48:16 +00:00
|
|
|
help="Device args, [default=%default]")
|
2013-04-28 10:41:35 +00:00
|
|
|
parser.add_option("-A", "--antenna", type="string", default=None,
|
2013-05-09 11:48:16 +00:00
|
|
|
help="Select RX antenna where appropriate")
|
2016-02-28 16:46:55 +00:00
|
|
|
parser.add_option("", "--clock-source",
|
|
|
|
help="Set the clock source; typically 'internal', 'external', 'external_1pps', 'mimo' or 'gpsdo'")
|
2013-05-09 11:48:16 +00:00
|
|
|
parser.add_option("-s", "--samp-rate", type="eng_float", default=None,
|
|
|
|
help="Set sample rate (bandwidth), minimum by default")
|
|
|
|
parser.add_option("-f", "--center-freq", type="eng_float", default=None,
|
|
|
|
help="Set frequency to FREQ", metavar="FREQ")
|
2013-12-27 23:56:11 +00:00
|
|
|
parser.add_option("-c", "--freq-corr", type="eng_float", default=None,
|
2013-05-09 11:48:16 +00:00
|
|
|
help="Set frequency correction (ppm)")
|
2013-04-28 10:41:35 +00:00
|
|
|
parser.add_option("-g", "--gain", type="eng_float", default=None,
|
2013-05-09 11:48:16 +00:00
|
|
|
help="Set gain in dB (default is midpoint)")
|
2014-03-11 09:46:18 +00:00
|
|
|
parser.add_option("-G", "--gains", type="string", default=None,
|
|
|
|
help="Set named gain in dB, name:gain,name:gain,...")
|
2014-03-14 09:51:15 +00:00
|
|
|
parser.add_option("-r", "--record", type="string", default="/tmp/name-f%F-s%S-t%T.cfile",
|
|
|
|
help="Filename to record to, available wildcards: %S: sample rate, %F: center frequency, %T: timestamp, Example: /tmp/name-f%F-s%S-t%T.cfile")
|
2013-12-27 23:56:11 +00:00
|
|
|
parser.add_option("", "--dc-offset-mode", type="int", default=None,
|
|
|
|
help="Set the RX frontend DC offset correction mode")
|
|
|
|
parser.add_option("", "--iq-balance-mode", type="int", default=None,
|
|
|
|
help="Set the RX frontend IQ imbalance correction mode")
|
2013-04-28 10:41:35 +00:00
|
|
|
parser.add_option("-W", "--waterfall", action="store_true", default=False,
|
|
|
|
help="Enable waterfall display")
|
2013-10-13 20:16:59 +00:00
|
|
|
parser.add_option("-F", "--fosphor", action="store_true", default=False,
|
|
|
|
help="Enable fosphor display")
|
2013-04-28 10:41:35 +00:00
|
|
|
parser.add_option("-S", "--oscilloscope", action="store_true", default=False,
|
|
|
|
help="Enable oscilloscope display")
|
2020-02-03 11:58:31 +00:00
|
|
|
parser.add_option("-Q", "--qtgui", action="store_true", default=False,
|
|
|
|
help="Enable QTgui 'all-in-one' display")
|
2013-04-28 10:41:35 +00:00
|
|
|
parser.add_option("", "--avg-alpha", type="eng_float", default=1e-1,
|
|
|
|
help="Set fftsink averaging factor, default=[%default]")
|
2013-12-27 23:56:11 +00:00
|
|
|
parser.add_option("", "--averaging", action="store_true", default=False,
|
2013-04-28 10:41:35 +00:00
|
|
|
help="Enable fftsink averaging, default=[%default]")
|
2016-02-28 16:50:54 +00:00
|
|
|
parser.add_option("", "--peak-hold", action="store_true", default=False,
|
|
|
|
help="Enable fftsink peak hold, default=[%default]")
|
2013-04-28 10:41:35 +00:00
|
|
|
parser.add_option("", "--ref-scale", type="eng_float", default=1.0,
|
|
|
|
help="Set dBFS=0dB input value, default=[%default]")
|
|
|
|
parser.add_option("", "--fft-size", type="int", default=1024,
|
|
|
|
help="Set number of FFT bins [default=%default]")
|
|
|
|
parser.add_option("", "--fft-rate", type="int", default=30,
|
|
|
|
help="Set FFT update rate, [default=%default]")
|
2013-05-09 11:48:16 +00:00
|
|
|
parser.add_option("-v", "--verbose", action="store_true", default=False,
|
|
|
|
help="Use verbose console output [default=%default]")
|
2013-04-28 10:41:35 +00:00
|
|
|
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
if len(args) != 0:
|
|
|
|
parser.print_help()
|
|
|
|
sys.exit(1)
|
|
|
|
self.options = options
|
2013-05-09 11:48:16 +00:00
|
|
|
|
|
|
|
self._verbose = options.verbose
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
try:
|
|
|
|
self.src = osmosdr.source(options.args)
|
|
|
|
except RuntimeError:
|
|
|
|
print("Couldn't instanciate source (no device present?).", file=sys.stderr)
|
|
|
|
sys.exit(1)
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2013-06-05 17:38:57 +00:00
|
|
|
try:
|
|
|
|
self.src.get_sample_rates().start()
|
|
|
|
except RuntimeError:
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Source has no sample rates (wrong device arguments?).", file=sys.stderr)
|
2013-06-05 17:38:57 +00:00
|
|
|
sys.exit(1)
|
|
|
|
|
2013-04-28 10:41:35 +00:00
|
|
|
# Set the antenna
|
2020-02-03 11:58:31 +00:00
|
|
|
if options.antenna:
|
2013-05-09 11:48:16 +00:00
|
|
|
self.src.set_antenna(options.antenna)
|
|
|
|
|
2016-02-28 16:46:55 +00:00
|
|
|
# Set the clock source:
|
|
|
|
if options.clock_source is not None:
|
|
|
|
self.src.set_clock_source(options.clock_source)
|
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
if options.samp_rate is None:
|
|
|
|
options.samp_rate = self.src.get_sample_rates().start()
|
|
|
|
|
|
|
|
if options.gain is None:
|
2013-10-26 12:51:05 +00:00
|
|
|
gain = self.src.get_gain()
|
|
|
|
if gain is None:
|
|
|
|
# if no gain was specified, use the mid-point in dB
|
|
|
|
r = self.src.get_gain_range()
|
|
|
|
try: # empty gain range returned in file= mode
|
|
|
|
options.gain = float(r.start()+r.stop())/2
|
|
|
|
except RuntimeError:
|
|
|
|
options.gain = 0
|
|
|
|
else:
|
|
|
|
options.gain = gain
|
2013-05-09 11:48:16 +00:00
|
|
|
|
2014-03-11 09:46:18 +00:00
|
|
|
self.src.set_gain(options.gain)
|
|
|
|
|
|
|
|
if self._verbose:
|
|
|
|
gain_names = self.src.get_gain_names()
|
|
|
|
for name in gain_names:
|
2020-02-03 11:58:31 +00:00
|
|
|
rg = self.src.get_gain_range(name)
|
|
|
|
print("%s gain range: start %g stop %g step %g" % (name, rg.start(), rg.stop(), rg.step()))
|
2014-03-11 09:46:18 +00:00
|
|
|
|
|
|
|
if options.gains:
|
|
|
|
for tuple in options.gains.split(","):
|
|
|
|
name, gain = tuple.split(":")
|
|
|
|
gain = int(gain)
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Setting gain %s to %g." % (name, gain))
|
2014-03-11 09:46:18 +00:00
|
|
|
self.src.set_gain(gain, name)
|
|
|
|
|
|
|
|
if self._verbose:
|
|
|
|
rates = self.src.get_sample_rates()
|
2020-02-03 11:58:31 +00:00
|
|
|
print('Supported sample rates %.10g-%.10g step %.10g.' % (rates.start(), rates.stop(), rates.step()))
|
|
|
|
|
|
|
|
self.bandwidth_ok = True
|
|
|
|
try:
|
|
|
|
rg = self.src.get_bandwidth_range()
|
|
|
|
range_start = rg.start()
|
|
|
|
if self._verbose:
|
|
|
|
print('Supported bandwidth rates %.10g-%.10g step %.10g.' % (rg.start(), rg.stop(), rg.step()))
|
|
|
|
except RuntimeError as ex:
|
|
|
|
self.bandwidth_ok = False
|
2014-03-11 09:46:18 +00:00
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
if options.center_freq is None:
|
2013-10-26 12:51:05 +00:00
|
|
|
freq = self.src.get_center_freq()
|
|
|
|
if freq != 0:
|
|
|
|
options.center_freq = freq
|
|
|
|
else:
|
|
|
|
# if no freq was specified, use the mid-point in Hz
|
|
|
|
r = self.src.get_freq_range()
|
|
|
|
options.center_freq = float(r.start()+r.stop())/2
|
2020-02-03 11:58:31 +00:00
|
|
|
if self._verbose:
|
|
|
|
print("Using auto-calculated mid-point frequency")
|
2013-05-09 11:48:16 +00:00
|
|
|
|
|
|
|
input_rate = self.src.set_sample_rate(options.samp_rate)
|
2013-09-28 11:40:03 +00:00
|
|
|
self.src.set_bandwidth(input_rate)
|
2013-05-09 11:48:16 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
if self._verbose:
|
|
|
|
ranges = self.src.get_freq_range()
|
|
|
|
print("Supported frequencies %s-%s"%(eng_notation.num_to_str(ranges.start()), eng_notation.num_to_str(ranges.stop())))
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
for name in self.src.get_gain_names():
|
|
|
|
print("GAIN(%s): %g"%(name, self.src.get_gain(name)))
|
2013-05-09 11:48:16 +00:00
|
|
|
|
2014-03-11 09:46:18 +00:00
|
|
|
# initialize values from options
|
2020-02-03 11:58:31 +00:00
|
|
|
if options.freq_corr is not None:
|
|
|
|
self.set_freq_corr(options.freq_corr)
|
2013-05-09 11:48:16 +00:00
|
|
|
|
2013-12-27 23:56:11 +00:00
|
|
|
self.dc_offset_mode = options.dc_offset_mode
|
|
|
|
self.iq_balance_mode = options.iq_balance_mode
|
|
|
|
|
|
|
|
# initialize reasonable defaults for DC / IQ correction
|
2020-02-03 11:58:31 +00:00
|
|
|
self.dc_offset_real = 0
|
|
|
|
self.dc_offset_imag = 0
|
|
|
|
self.iq_balance_mag = 0
|
|
|
|
self.iq_balance_pha = 0
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2013-10-13 20:16:59 +00:00
|
|
|
if options.fosphor:
|
|
|
|
from gnuradio import fosphor
|
2020-02-03 11:58:31 +00:00
|
|
|
self.scope = fosphor.qt_sink_c()
|
|
|
|
self.scope.set_frequency_range(0, input_rate)
|
|
|
|
self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
|
|
|
|
self.scope_win.setMinimumSize(800, 300)
|
2013-10-13 20:16:59 +00:00
|
|
|
elif options.waterfall:
|
2020-02-03 11:58:31 +00:00
|
|
|
self.scope = qtgui.waterfall_sink_c(
|
|
|
|
options.fft_size,
|
2020-12-30 15:16:01 +00:00
|
|
|
wintype=window.WIN_BLACKMAN_hARRIS,
|
2020-02-03 11:58:31 +00:00
|
|
|
fc=0,
|
|
|
|
bw=input_rate,
|
|
|
|
name="",
|
|
|
|
nconnections=1
|
|
|
|
)
|
|
|
|
self.scope.enable_grid(False)
|
|
|
|
self.scope.enable_axis_labels(True)
|
|
|
|
self.scope.set_intensity_range(-100, 20)
|
|
|
|
self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
|
|
|
|
self.scope_win.setMinimumSize(800, 420)
|
|
|
|
|
2013-04-28 10:41:35 +00:00
|
|
|
elif options.oscilloscope:
|
2020-02-03 11:58:31 +00:00
|
|
|
self.scope = qtgui.time_sink_c(
|
|
|
|
options.fft_size,
|
|
|
|
samp_rate=input_rate,
|
|
|
|
name="",
|
|
|
|
nconnections=1
|
|
|
|
)
|
|
|
|
self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
|
|
|
|
self.scope_win.setMinimumSize(800, 600)
|
|
|
|
|
|
|
|
elif options.qtgui:
|
|
|
|
self.scope = qtgui.sink_c(
|
|
|
|
options.fft_size,
|
2020-12-30 15:16:01 +00:00
|
|
|
wintype=window.WIN_BLACKMAN_hARRIS,
|
2020-02-03 11:58:31 +00:00
|
|
|
fc=0,
|
|
|
|
bw=input_rate,
|
|
|
|
name="",
|
|
|
|
plotfreq=True,
|
|
|
|
plotwaterfall=True,
|
|
|
|
plottime=True,
|
|
|
|
plotconst=True
|
|
|
|
)
|
|
|
|
self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
|
|
|
|
self.scope.set_update_time(1.0/10)
|
|
|
|
self.scope_win.setMinimumSize(800, 600)
|
|
|
|
|
2013-04-28 10:41:35 +00:00
|
|
|
else:
|
2020-02-03 11:58:31 +00:00
|
|
|
self.scope = qtgui.freq_sink_c(
|
|
|
|
fftsize=options.fft_size,
|
2020-12-30 15:16:01 +00:00
|
|
|
wintype=window.WIN_BLACKMAN_hARRIS,
|
2020-02-03 11:58:31 +00:00
|
|
|
fc=0,
|
|
|
|
bw=input_rate,
|
|
|
|
name="",
|
|
|
|
nconnections=1
|
|
|
|
)
|
|
|
|
self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
|
|
|
|
self.scope.disable_legend()
|
|
|
|
self.scope_win.setMinimumSize(800, 420)
|
|
|
|
|
|
|
|
self.connect((self.src, 0), (self.scope, 0))
|
|
|
|
try:
|
|
|
|
self.freq = freq_recv(self.set_freq)
|
|
|
|
self.msg_connect((self.scope, 'freq'), (self.freq, 'msg'))
|
|
|
|
except RuntimeError:
|
|
|
|
self.freq = None
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2014-03-14 09:51:15 +00:00
|
|
|
self.file_sink = blocks.file_sink(gr.sizeof_gr_complex, "/dev/null", False)
|
|
|
|
self.file_sink.set_unbuffered(False)
|
|
|
|
self.file_sink.close() # close the sink immediately
|
|
|
|
# lock/connect/unlock at record button event did not work, so we leave it connected at all times
|
|
|
|
self.connect(self.src, self.file_sink)
|
2014-03-11 09:46:18 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
self._build_gui()
|
2013-12-27 23:56:11 +00:00
|
|
|
|
2013-04-28 10:41:35 +00:00
|
|
|
# set initial values
|
2020-02-03 11:58:31 +00:00
|
|
|
if not self.set_freq(options.center_freq):
|
2013-04-28 10:41:35 +00:00
|
|
|
self._set_status_msg("Failed to set initial frequency")
|
2020-02-03 11:58:31 +00:00
|
|
|
if options.record is not None:
|
|
|
|
self._fre.insert(options.record)
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2014-03-14 09:51:15 +00:00
|
|
|
def record_to_filename(self):
|
2020-02-03 11:58:31 +00:00
|
|
|
s = self._fre.text()
|
2014-03-14 09:51:15 +00:00
|
|
|
s = s.replace('%S', '%e' % self.src.get_sample_rate())
|
|
|
|
s = s.replace('%F', '%e' % self.src.get_center_freq())
|
|
|
|
s = s.replace('%T', datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
|
|
|
|
return s
|
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
def _set_status_msg(self, msg, timeout=0):
|
|
|
|
self.status.showMessage(msg, timeout)
|
2013-06-08 14:39:47 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
def _shrink(self, widget):
|
|
|
|
"""Try to shrink RangeWidget by removing unnecessary margins"""
|
|
|
|
try:
|
|
|
|
widget.layout().setContentsMargins(0, 0, 0, 0)
|
|
|
|
widget.children()[0].layout().setContentsMargins(0, 0, 0, 0)
|
|
|
|
except:
|
|
|
|
pass
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
def _add_section(self, text, layout):
|
|
|
|
"""Add a section header to the GUI"""
|
|
|
|
frame = Qt.QWidget()
|
|
|
|
frame_layout = Qt.QHBoxLayout()
|
|
|
|
frame_layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
frame.setLayout(frame_layout)
|
|
|
|
|
|
|
|
wid = Qt.QLabel()
|
|
|
|
wid.setText(text)
|
|
|
|
wid.setStyleSheet("font-weight: bold;")
|
|
|
|
frame_layout.addWidget(wid)
|
|
|
|
wid = Qt.QFrame()
|
|
|
|
wid.setFrameShape(Qt.QFrame.HLine)
|
|
|
|
frame_layout.addWidget(wid)
|
|
|
|
frame_layout.setStretchFactor(wid, 1)
|
|
|
|
|
|
|
|
layout.addWidget(frame)
|
|
|
|
|
|
|
|
def _chooser(self, names, callback, default=0):
|
|
|
|
"""A simple radio-button chooser"""
|
|
|
|
buttons = Qt.QWidget()
|
|
|
|
blayout = Qt.QHBoxLayout()
|
|
|
|
bgroup = Qt.QButtonGroup()
|
|
|
|
buttons.setObjectName("foo")
|
|
|
|
buttons.setStyleSheet("QWidget#foo {border: 1px outset grey;}")
|
|
|
|
buttons.setLayout(blayout)
|
|
|
|
chooser = []
|
|
|
|
for (num, txt) in enumerate(names):
|
|
|
|
rb = Qt.QRadioButton(txt)
|
|
|
|
rb.clicked.connect(partial(callback, num))
|
|
|
|
chooser.append(rb)
|
|
|
|
bgroup.addButton(rb,num)
|
|
|
|
blayout.addWidget(rb)
|
|
|
|
if num == default:
|
|
|
|
rb.setChecked(True)
|
|
|
|
return buttons
|
|
|
|
|
|
|
|
def _build_gui(self):
|
|
|
|
|
|
|
|
self.top_widget = Qt.QWidget()
|
|
|
|
|
|
|
|
self.top_layout = Qt.QVBoxLayout(self.top_widget)
|
|
|
|
self.top_layout.addWidget(self.scope_win)
|
|
|
|
|
|
|
|
self.setCentralWidget(self.top_widget)
|
|
|
|
|
|
|
|
self.status = Qt.QStatusBar()
|
|
|
|
self.setStatusBar(self.status)
|
|
|
|
self.status.setStyleSheet("QStatusBar{border-top: 1px outset grey;}")
|
|
|
|
|
|
|
|
if hasattr(RangeWidget, 'EngSlider'):
|
|
|
|
eng_widget="eng_slider"
|
|
|
|
else:
|
|
|
|
eng_widget="counter_slider"
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
##################################################
|
|
|
|
# Frequency controls
|
|
|
|
##################################################
|
2020-02-03 11:58:31 +00:00
|
|
|
self._add_section("Frequency", self.top_layout)
|
2013-10-18 17:47:21 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
r = self.src.get_freq_range()
|
|
|
|
self._fr = Range(r.start(), r.stop(), (r.start()+r.stop())/100, self.src.get_center_freq(), 200)
|
|
|
|
self._fw = RangeWidget(self._fr, self.set_freq, 'Center Frequency (Hz)', eng_widget, float)
|
|
|
|
self._shrink(self._fw)
|
|
|
|
self.top_layout.addWidget(self._fw)
|
2013-05-09 11:48:16 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
if hasattr(self, 'ppm') and self.ppm is not None:
|
|
|
|
self._fcr = Range(-100, 100, 0.1, self.src.get_freq_corr(), 200)
|
|
|
|
self._fcw = RangeWidget(self._fcr, self.set_freq_corr, 'Freq. Correction (ppm)', "counter_slider", float)
|
|
|
|
self._shrink(self._fcw)
|
|
|
|
self.top_layout.addWidget(self._fcw)
|
2013-05-09 11:48:16 +00:00
|
|
|
|
|
|
|
##################################################
|
|
|
|
# Gain controls
|
|
|
|
##################################################
|
2020-02-03 11:58:31 +00:00
|
|
|
self._add_section("Gains", self.top_layout)
|
|
|
|
|
|
|
|
self._gr={}
|
|
|
|
self._gw={}
|
|
|
|
for gain_name in self.src.get_gain_names():
|
|
|
|
rg = self.src.get_gain_range(gain_name)
|
|
|
|
self._gr[gain_name] = Range(rg.start(), rg.stop(), rg.step(), self.src.get_gain(gain_name), 100)
|
|
|
|
self._gw[gain_name] = RangeWidget(self._gr[gain_name], partial(self.set_named_gain,name=gain_name), '%s Gain (dB):'%gain_name, "counter_slider", float)
|
|
|
|
self._shrink(self._gw[gain_name])
|
|
|
|
self._gw[gain_name].d_widget.counter.setDecimals(2)
|
|
|
|
self.top_layout.addWidget(self._gw[gain_name])
|
2013-05-09 11:48:16 +00:00
|
|
|
|
|
|
|
##################################################
|
|
|
|
# Bandwidth controls
|
|
|
|
##################################################
|
2020-02-03 11:58:31 +00:00
|
|
|
if self.bandwidth_ok:
|
|
|
|
self._add_section("Bandwidth", self.top_layout)
|
2013-05-09 11:48:16 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
r = self.src.get_bandwidth_range()
|
|
|
|
self._bwr = Range(r.start(), r.stop(), r.step() or (r.stop() - r.start())/100, self.src.get_bandwidth(), 100)
|
|
|
|
self._bww = RangeWidget(self._bwr, self.set_bandwidth, 'Bandwidth (Hz):', eng_widget, float)
|
|
|
|
self._shrink(self._bww)
|
|
|
|
self.top_layout.addWidget(self._bww)
|
2013-05-09 11:48:16 +00:00
|
|
|
|
|
|
|
##################################################
|
|
|
|
# Sample rate controls
|
|
|
|
##################################################
|
2020-02-03 11:58:31 +00:00
|
|
|
self._add_section("Sample Rate", self.top_layout)
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
r = self.src.get_sample_rates()
|
2020-02-12 09:55:23 +00:00
|
|
|
self._srr = Range(r.start(), r.stop(), r.step() or (r.stop() - r.start())/100, self.src.get_sample_rate(), 100)
|
2020-02-03 11:58:31 +00:00
|
|
|
self._srw = RangeWidget(self._srr, self.set_sample_rate, 'Sample Rate (Hz)', eng_widget, float)
|
|
|
|
self._shrink(self._srw)
|
|
|
|
self.top_layout.addWidget(self._srw)
|
2014-03-11 09:46:18 +00:00
|
|
|
|
|
|
|
##################################################
|
|
|
|
# File recording controls
|
|
|
|
##################################################
|
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
self._add_section("File recording", self.top_layout)
|
|
|
|
|
|
|
|
wid = Qt.QWidget()
|
|
|
|
|
|
|
|
layout = Qt.QHBoxLayout()
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
self._frl = Qt.QLabel('File Name')
|
|
|
|
layout.addWidget(self._frl)
|
|
|
|
|
|
|
|
self._fre = Qt.QLineEdit()
|
|
|
|
layout.addWidget(self._fre)
|
|
|
|
|
|
|
|
self._frb = Qt.QPushButton('REC')
|
|
|
|
layout.addWidget(self._frb)
|
|
|
|
|
|
|
|
wid.setLayout(layout)
|
|
|
|
self.top_layout.addWidget(wid)
|
|
|
|
|
|
|
|
self.recording = 0
|
|
|
|
def record_callback():
|
|
|
|
self.recording = 1-self.recording
|
|
|
|
if self.recording:
|
|
|
|
self._srw.setDisabled(True)
|
|
|
|
self._fre.setDisabled(True)
|
|
|
|
self._frb.setText('STOP')
|
2014-03-11 09:46:18 +00:00
|
|
|
|
2014-03-14 09:51:15 +00:00
|
|
|
self.rec_file_name = self.record_to_filename()
|
2014-03-11 09:46:18 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Recording samples to ", self.rec_file_name)
|
2014-03-14 09:51:15 +00:00
|
|
|
self.file_sink.open(self.rec_file_name);
|
|
|
|
else:
|
2020-02-03 11:58:31 +00:00
|
|
|
self._srw.setDisabled(False)
|
|
|
|
self._fre.setDisabled(False)
|
|
|
|
self._frb.setText('REC')
|
2014-03-14 09:51:15 +00:00
|
|
|
|
|
|
|
self.file_sink.close()
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Finished recording to", self.rec_file_name)
|
2014-03-14 09:51:15 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
self._fre.returnPressed.connect(record_callback)
|
|
|
|
self._frb.clicked.connect(record_callback)
|
2014-03-11 09:46:18 +00:00
|
|
|
|
2013-12-27 23:56:11 +00:00
|
|
|
##################################################
|
|
|
|
# DC Offset controls
|
|
|
|
##################################################
|
|
|
|
|
|
|
|
if self.dc_offset_mode != None:
|
2020-02-03 11:58:31 +00:00
|
|
|
self._add_section("DC Offset Correction", self.top_layout)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
wid = Qt.QWidget()
|
|
|
|
|
|
|
|
layout = Qt.QHBoxLayout()
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
self._dcb = self._chooser(["Off", "Manual", "Auto"], self.set_dc_offset_mode, self.dc_offset_mode)
|
|
|
|
layout.addWidget(self._dcb)
|
|
|
|
|
|
|
|
self._dcrr = Range(-1, +1, 0.001, 0, 20)
|
|
|
|
self._dcrw = RangeWidget(self._dcrr, self.set_dc_offset_real, 'Real', "counter_slider", float)
|
|
|
|
self._shrink(self._dcrw)
|
|
|
|
layout.addWidget(self._dcrw)
|
|
|
|
|
|
|
|
self._dcir = Range(-1, +1, 0.001, 0, 20)
|
|
|
|
self._dciw = RangeWidget(self._dcrr, self.set_dc_offset_imag, 'Imag', "counter_slider", float)
|
|
|
|
self._shrink(self._dciw)
|
|
|
|
layout.addWidget(self._dciw)
|
|
|
|
|
|
|
|
wid.setLayout(layout)
|
|
|
|
self.top_layout.addWidget(wid)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
|
|
|
##################################################
|
|
|
|
# IQ Imbalance controls
|
|
|
|
##################################################
|
|
|
|
|
|
|
|
if self.iq_balance_mode != None:
|
2020-02-03 11:58:31 +00:00
|
|
|
self._add_section("IQ Imbalance Correction", self.top_layout)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
wid = Qt.QWidget()
|
|
|
|
|
|
|
|
layout = Qt.QHBoxLayout()
|
|
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
|
|
|
|
|
|
self._iqb = self._chooser(["Off", "Manual", "Auto"], self.set_dc_offset_mode, self.iq_balance_mode)
|
|
|
|
layout.addWidget(self._iqb)
|
|
|
|
|
|
|
|
self._iqmr = Range(-1, +1, 0.001, 0, 20)
|
|
|
|
self._iqmw = RangeWidget(self._iqmr, self.set_iq_balance_mag, 'Mag', "counter_slider", float)
|
|
|
|
self._shrink(self._iqmw)
|
|
|
|
layout.addWidget(self._iqmw)
|
|
|
|
|
|
|
|
self._iqpr = Range(-1, +1, 0.001, 0, 20)
|
|
|
|
self._iqpw = RangeWidget(self._iqpr, self.set_iq_balance_pha, 'Pha', "counter_slider", float)
|
|
|
|
self._shrink(self._iqpw)
|
|
|
|
layout.addWidget(self._iqpw)
|
|
|
|
|
|
|
|
wid.setLayout(layout)
|
|
|
|
self.top_layout.addWidget(wid)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
|
|
|
def set_dc_offset_mode(self, dc_offset_mode):
|
|
|
|
if dc_offset_mode == 1:
|
2020-02-03 11:58:31 +00:00
|
|
|
self._dcrw.setDisabled(False)
|
|
|
|
self._dciw.setDisabled(False)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
self.set_dc_offset()
|
2013-12-27 23:56:11 +00:00
|
|
|
else:
|
2020-02-03 11:58:31 +00:00
|
|
|
self._dcrw.setDisabled(True)
|
|
|
|
self._dciw.setDisabled(True)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
|
|
|
self.dc_offset_mode = dc_offset_mode
|
|
|
|
self.src.set_dc_offset_mode(dc_offset_mode)
|
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
def set_dc_offset_real(self, value):
|
|
|
|
self.dc_offset_real = value
|
|
|
|
self.set_dc_offset()
|
|
|
|
|
|
|
|
def set_dc_offset_imag(self, value):
|
|
|
|
self.dc_offset_imag = value
|
|
|
|
self.set_dc_offset()
|
|
|
|
|
|
|
|
def set_dc_offset(self):
|
|
|
|
correction = complex(self.dc_offset_real, self.dc_offset_imag)
|
2014-01-11 08:06:20 +00:00
|
|
|
|
|
|
|
try:
|
2020-02-03 11:58:31 +00:00
|
|
|
self.src.set_dc_offset(correction)
|
2014-01-11 08:06:20 +00:00
|
|
|
|
|
|
|
if self._verbose:
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Set DC offset to", correction)
|
2014-01-11 08:06:20 +00:00
|
|
|
except RuntimeError as ex:
|
2020-02-03 11:58:31 +00:00
|
|
|
print(ex)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
|
|
|
def set_iq_balance_mode(self, iq_balance_mode):
|
|
|
|
if iq_balance_mode == 1:
|
2020-02-03 11:58:31 +00:00
|
|
|
self._iqpw.setDisabled(False)
|
|
|
|
self._iqmw.setDisabled(False)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
self.set_iq_balance()
|
2013-12-27 23:56:11 +00:00
|
|
|
else:
|
2020-02-03 11:58:31 +00:00
|
|
|
self._iqpw.setDisabled(True)
|
|
|
|
self._iqmw.setDisabled(True)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
|
|
|
self.iq_balance_mode = iq_balance_mode
|
|
|
|
self.src.set_iq_balance_mode(iq_balance_mode)
|
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
def set_iq_balance_mag(self, value):
|
|
|
|
self.iq_balance_mag = value
|
|
|
|
self.set_iq_balance()
|
|
|
|
|
|
|
|
def set_iq_balance_pha(self, value):
|
|
|
|
self.iq_balance_pha = value
|
|
|
|
self.set_iq_balance()
|
|
|
|
|
|
|
|
def set_iq_balance(self):
|
|
|
|
correction = complex(self.iq_balance_mag, self.iq_balance_pha)
|
2014-01-11 08:06:20 +00:00
|
|
|
|
|
|
|
try:
|
2020-02-03 11:58:31 +00:00
|
|
|
self.src.set_iq_balance(correction)
|
2014-01-11 08:06:20 +00:00
|
|
|
|
|
|
|
if self._verbose:
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Set IQ balance to", correction)
|
2014-01-11 08:06:20 +00:00
|
|
|
except RuntimeError as ex:
|
2020-02-03 11:58:31 +00:00
|
|
|
print(ex)
|
2013-12-27 23:56:11 +00:00
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
def set_sample_rate(self, samp_rate):
|
|
|
|
samp_rate = self.src.set_sample_rate(samp_rate)
|
2020-02-03 11:58:31 +00:00
|
|
|
if hasattr(self.scope, 'set_frequency_range'):
|
|
|
|
self.scope.set_frequency_range(self.src.get_center_freq(), samp_rate)
|
2013-10-13 20:16:59 +00:00
|
|
|
if hasattr(self.scope, 'set_sample_rate'):
|
|
|
|
self.scope.set_sample_rate(samp_rate)
|
2013-05-09 11:48:16 +00:00
|
|
|
if self._verbose:
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Set sample rate to:", samp_rate)
|
2013-09-28 11:40:03 +00:00
|
|
|
|
|
|
|
try:
|
2020-02-03 11:58:31 +00:00
|
|
|
if hasattr(self._bww.d_widget, 'setValue'):
|
|
|
|
self._bww.d_widget.setValue(samp_rate)
|
|
|
|
else:
|
|
|
|
self._bww.d_widget.counter.setValue(samp_rate)
|
|
|
|
except (RuntimeError, AttributeError):
|
2013-09-28 11:40:03 +00:00
|
|
|
pass
|
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
return samp_rate
|
|
|
|
|
|
|
|
def set_named_gain(self, gain, name):
|
2020-02-03 11:58:31 +00:00
|
|
|
if self._verbose:
|
|
|
|
print("Trying to set " + name + " gain to:", gain)
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
gain = self.src.set_gain(gain, name)
|
|
|
|
if self._verbose:
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Set " + name + " gain to:", gain)
|
2013-05-09 11:48:16 +00:00
|
|
|
|
|
|
|
def set_bandwidth(self, bw):
|
2020-02-03 11:58:31 +00:00
|
|
|
if self._verbose:
|
|
|
|
print("Trying to set bandwidth to:", bw)
|
|
|
|
clipped_bw = self.src.get_bandwidth_range().clip(bw)
|
|
|
|
if self._verbose:
|
|
|
|
print("Clipping bandwidth to:", clipped_bw)
|
2013-06-08 14:39:47 +00:00
|
|
|
if self.src.get_bandwidth() != clipped_bw:
|
|
|
|
bw = self.src.set_bandwidth(clipped_bw)
|
|
|
|
|
|
|
|
if self._verbose:
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Set bandwidth to:", bw)
|
2013-06-08 14:39:47 +00:00
|
|
|
|
2013-09-28 11:40:03 +00:00
|
|
|
return bw
|
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
def set_freq(self, freq):
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
freq = self.src.set_center_freq(freq)
|
2013-04-28 10:41:35 +00:00
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
if hasattr(self.scope, 'set_frequency_range'):
|
|
|
|
self.scope.set_frequency_range(freq, self.src.get_sample_rate())
|
2013-10-13 20:16:59 +00:00
|
|
|
if hasattr(self.scope, 'set_baseband_freq'):
|
2013-05-09 11:48:16 +00:00
|
|
|
self.scope.set_baseband_freq(freq)
|
|
|
|
|
2020-02-03 11:58:31 +00:00
|
|
|
try:
|
|
|
|
if hasattr(self._fw.d_widget, 'setValue'):
|
|
|
|
self._fw.d_widget.setValue(freq)
|
|
|
|
else:
|
|
|
|
self._fw.d_widget.counter.setValue(freq)
|
|
|
|
except (RuntimeError, AttributeError):
|
|
|
|
pass
|
|
|
|
|
2013-05-09 11:48:16 +00:00
|
|
|
if freq is not None:
|
|
|
|
if self._verbose:
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Set center frequency to %.10g"%freq)
|
2013-05-09 11:48:16 +00:00
|
|
|
elif self._verbose:
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Failed to set freq.")
|
2013-05-09 11:48:16 +00:00
|
|
|
return freq
|
|
|
|
|
|
|
|
def set_freq_corr(self, ppm):
|
2020-02-03 11:58:31 +00:00
|
|
|
self.ppm = self.src.set_freq_corr(ppm)
|
2013-05-09 11:48:16 +00:00
|
|
|
if self._verbose:
|
2020-02-03 11:58:31 +00:00
|
|
|
print("Set frequency correction to:", self.ppm)
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
qapp = Qt.QApplication(sys.argv)
|
|
|
|
|
|
|
|
tb = app_top_block(qapp.arguments(), "osmocom Spectrum Browser")
|
|
|
|
tb.start()
|
|
|
|
tb.show()
|
|
|
|
|
|
|
|
def sig_handler(sig=None, frame=None):
|
|
|
|
print("caught signal")
|
|
|
|
Qt.QApplication.quit()
|
|
|
|
|
|
|
|
signal.signal(signal.SIGINT, sig_handler)
|
|
|
|
signal.signal(signal.SIGTERM, sig_handler)
|
|
|
|
|
|
|
|
# this timer is necessary for signals (^C) to work
|
|
|
|
timer = Qt.QTimer()
|
|
|
|
timer.start(500)
|
|
|
|
timer.timeout.connect(lambda: None)
|
|
|
|
|
|
|
|
def quitting():
|
|
|
|
tb.stop()
|
|
|
|
tb.wait()
|
|
|
|
qapp.aboutToQuit.connect(quitting)
|
|
|
|
qapp.exec_()
|
2013-04-28 10:41:35 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
2020-02-03 11:58:31 +00:00
|
|
|
main()
|