diff options
Diffstat (limited to 'apps/osmocom_fft')
-rwxr-xr-x | apps/osmocom_fft | 974 |
1 files changed, 555 insertions, 419 deletions
diff --git a/apps/osmocom_fft b/apps/osmocom_fft index 10a682c..da38861 100755 --- a/apps/osmocom_fft +++ b/apps/osmocom_fft @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # # Copyright 2012 Free Software Foundation, Inc. # @@ -20,71 +20,45 @@ # Boston, MA 02110-1301, USA. # +SAMP_RANGE_KEY = 'samp_range' +SAMP_RATE_KEY = 'samp_rate' +GAIN_KEY = lambda x: 'gain:'+x +BWIDTH_KEY = 'bwidth' +CENTER_FREQ_KEY = 'center_freq' +FREQ_CORR_KEY = 'freq_corr' +FREQ_RANGE_KEY = 'freq_range' +GAIN_RANGE_KEY = lambda x: 'gain_range:'+x +BWIDTH_RANGE_KEY = 'bwidth_range' + import osmosdr from gnuradio import blocks -from gnuradio import gr +from gnuradio import gr, gru from gnuradio import eng_notation -from gnuradio.filter import firdes +from gnuradio.gr.pubsub import pubsub from gnuradio.eng_option import eng_option from optparse import OptionParser -from functools import partial import sys -import signal +import numpy import time import datetime try: - from PyQt5 import Qt - from gnuradio import qtgui - import sip - from gnuradio.qtgui import Range, RangeWidget + from gnuradio.wxgui import stdgui2, form, slider + from gnuradio.wxgui import forms + from gnuradio.wxgui import fftsink2, waterfallsink2, scopesink2 + import wx except ImportError: - sys.stderr.write("Error importing GNU Radio's Qtgui.\n") + sys.stderr.write("Error importing GNU Radio's wxgui. Please make sure gr-wxgui is installed.\n") sys.exit(1) +class app_top_block(stdgui2.std_top_block, pubsub): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv) + pubsub.__init__(self) -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) + self.frame = frame + self.panel = panel parser = OptionParser(option_class=eng_option) parser.add_option("-a", "--args", type="string", default="", @@ -115,8 +89,6 @@ class app_top_block(gr.top_block, Qt.QMainWindow): help="Enable fosphor display") parser.add_option("-S", "--oscilloscope", action="store_true", default=False, help="Enable oscilloscope display") - parser.add_option("-Q", "--qtgui", action="store_true", default=False, - help="Enable QTgui 'all-in-one' display") parser.add_option("", "--avg-alpha", type="eng_float", default=1e-1, help="Set fftsink averaging factor, default=[%default]") parser.add_option("", "--averaging", action="store_true", default=False, @@ -140,20 +112,16 @@ class app_top_block(gr.top_block, Qt.QMainWindow): self._verbose = options.verbose - try: - self.src = osmosdr.source(options.args) - except RuntimeError: - print("Couldn't instanciate source (no device present?).", file=sys.stderr) - sys.exit(1) + self.src = osmosdr.source(options.args) try: self.src.get_sample_rates().start() except RuntimeError: - print("Source has no sample rates (wrong device arguments?).", file=sys.stderr) + print "Source has no sample rates (wrong device arguments?)." sys.exit(1) # Set the antenna - if options.antenna: + if(options.antenna): self.src.set_antenna(options.antenna) # Set the clock source: @@ -172,6 +140,7 @@ class app_top_block(gr.top_block, Qt.QMainWindow): options.gain = float(r.start()+r.stop())/2 except RuntimeError: options.gain = 0 + pass else: options.gain = gain @@ -180,28 +149,19 @@ class app_top_block(gr.top_block, Qt.QMainWindow): if self._verbose: gain_names = self.src.get_gain_names() for name in gain_names: - rg = self.src.get_gain_range(name) - print("%s gain range: start %g stop %g step %g" % (name, rg.start(), rg.stop(), rg.step())) + range = self.src.get_gain_range(name) + print "%s gain range: start %d stop %d step %d" % (name, range.start(), range.stop(), range.step()) if options.gains: for tuple in options.gains.split(","): name, gain = tuple.split(":") gain = int(gain) - print("Setting gain %s to %g." % (name, gain)) + print "Setting gain %s to %d." % (name, gain) self.src.set_gain(gain, name) if self._verbose: rates = self.src.get_sample_rates() - 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 + print 'Supported sample rates %d-%d step %d.' % (rates.start(), rates.stop(), rates.step()) if options.center_freq is None: freq = self.src.get_center_freq() @@ -211,99 +171,93 @@ class app_top_block(gr.top_block, Qt.QMainWindow): # 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 - if self._verbose: - print("Using auto-calculated mid-point frequency") input_rate = self.src.set_sample_rate(options.samp_rate) self.src.set_bandwidth(input_rate) - 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()))) + self.publish(SAMP_RANGE_KEY, self.src.get_sample_rates) + self.publish(FREQ_RANGE_KEY, self.src.get_freq_range) + for name in self.get_gain_names(): + self.publish(GAIN_RANGE_KEY(name), (lambda self=self,name=name: self.src.get_gain_range(name))) - for name in self.src.get_gain_names(): - print("GAIN(%s): %g"%(name, self.src.get_gain(name))) + self.publish(BWIDTH_RANGE_KEY, self.src.get_bandwidth_range) + + for name in self.get_gain_names(): + self.publish(GAIN_KEY(name), (lambda self=self,name=name: self.src.get_gain(name))) + + self.publish(BWIDTH_KEY, self.src.get_bandwidth) # initialize values from options - if options.freq_corr is not None: - self.set_freq_corr(options.freq_corr) + self[SAMP_RANGE_KEY] = self.src.get_sample_rates() + self[SAMP_RATE_KEY] = options.samp_rate + self[CENTER_FREQ_KEY] = options.center_freq + self[FREQ_CORR_KEY] = options.freq_corr + self['record'] = options.record self.dc_offset_mode = options.dc_offset_mode self.iq_balance_mode = options.iq_balance_mode # initialize reasonable defaults for DC / IQ correction - self.dc_offset_real = 0 - self.dc_offset_imag = 0 - self.iq_balance_mag = 0 - self.iq_balance_pha = 0 + self['dc_offset_real'] = 0 + self['dc_offset_imag'] = 0 + self['iq_balance_mag'] = 0 + self['iq_balance_pha'] = 0 + + #subscribe set methods + self.subscribe(SAMP_RATE_KEY, self.set_sample_rate) + + for name in self.get_gain_names(): + self.subscribe(GAIN_KEY(name), (lambda gain,self=self,name=name: self.set_named_gain(gain, name))) + + self.subscribe(BWIDTH_KEY, self.set_bandwidth) + self.subscribe(CENTER_FREQ_KEY, self.set_freq) + self.subscribe(FREQ_CORR_KEY, self.set_freq_corr) + + self.subscribe('dc_offset_real', self.set_dc_offset) + self.subscribe('dc_offset_imag', self.set_dc_offset) + self.subscribe('iq_balance_mag', self.set_iq_balance) + self.subscribe('iq_balance_pha', self.set_iq_balance) + + #force update on pubsub keys + #for key in (SAMP_RATE_KEY, BWIDTH_KEY, CENTER_FREQ_KEY, FREQ_CORR_KEY): + #print key, "=", self[key] + #self[key] = self[key] if options.fosphor: from gnuradio import fosphor - 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) + self.scope = fosphor.wx_sink_c(panel, size=(800,300)) + self.scope.set_sample_rate(input_rate) + self.frame.SetMinSize((800,600)) elif options.waterfall: - self.scope = qtgui.waterfall_sink_c( - options.fft_size, - wintype=firdes.WIN_BLACKMAN_hARRIS, - 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) - + self.scope = waterfallsink2.waterfall_sink_c (panel, + fft_size=options.fft_size, + sample_rate=input_rate, + ref_scale=options.ref_scale, + ref_level=20.0, + y_divs = 12) + + self.scope.set_callback(self.wxsink_callback) + self.frame.SetMinSize((800, 420)) elif options.oscilloscope: - 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, - wintype=firdes.WIN_BLACKMAN_hARRIS, - 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) - + self.scope = scopesink2.scope_sink_c(panel, sample_rate=input_rate) + self.frame.SetMinSize((800, 600)) else: - self.scope = qtgui.freq_sink_c( - fftsize=options.fft_size, - wintype=firdes.WIN_BLACKMAN_hARRIS, - 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 + self.scope = fftsink2.fft_sink_c (panel, + fft_size=options.fft_size, + sample_rate=input_rate, + ref_scale=options.ref_scale, + ref_level=20.0, + y_divs = 12, + average=options.averaging, + peak_hold=options.peak_hold, + avg_alpha=options.avg_alpha, + fft_rate=options.fft_rate) + + self.scope.set_callback(self.wxsink_callback) + self.frame.SetMinSize((800, 420)) + + self.connect(self.src, self.scope) self.file_sink = blocks.file_sink(gr.sizeof_gr_complex, "/dev/null", False) self.file_sink.set_unbuffered(False) @@ -311,404 +265,586 @@ class app_top_block(gr.top_block, Qt.QMainWindow): # 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) - self._build_gui() + self._build_gui(vbox) + + if self.dc_offset_mode != None: + self.set_dc_offset_mode(self.dc_offset_mode) + + if self.iq_balance_mode != None: + self.set_iq_balance_mode(self.iq_balance_mode) # set initial values - if not self.set_freq(options.center_freq): + if not(self.set_freq(options.center_freq)): self._set_status_msg("Failed to set initial frequency") - if options.record is not None: - self._fre.insert(options.record) def record_to_filename(self): - s = self._fre.text() + s = self['record'] 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 - def _set_status_msg(self, msg, timeout=0): - self.status.showMessage(msg, timeout) + def wxsink_callback(self, x, y): + self.set_freq_from_callback(x) - 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 + def _set_status_msg(self, msg): + self.frame.GetStatusBar().SetStatusText(msg, 0) - 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" + def _build_gui(self, vbox): + + if hasattr(self.scope, 'win'): + vbox.Add(self.scope.win, 1, wx.EXPAND) + vbox.AddSpacer(3) + + # add control area at the bottom + self.myform = myform = form.form() ################################################## # Frequency controls ################################################## - self._add_section("Frequency", self.top_layout) + fc_vbox = forms.static_box_sizer(parent=self.panel, + label="Center Frequency", + orient=wx.VERTICAL, + bold=True) + fc_vbox.AddSpacer(3) + # First row of frequency controls (center frequency) + freq_hbox = wx.BoxSizer(wx.HORIZONTAL) + fc_vbox.Add(freq_hbox, 0, wx.EXPAND) + fc_vbox.AddSpacer(5) + # Second row of frequency controls (freq. correction) + corr_hbox = wx.BoxSizer(wx.HORIZONTAL) + fc_vbox.Add(corr_hbox, 0, wx.EXPAND) + fc_vbox.AddSpacer(3) + + # Add frequency controls to top window sizer + vbox.Add(fc_vbox, 0, wx.EXPAND) + vbox.AddSpacer(5) + + freq_hbox.AddSpacer(3) + forms.text_box( + parent=self.panel, sizer=freq_hbox, + label='Center Frequency (Hz)', + proportion=1, + converter=forms.float_converter(), + ps=self, + key=CENTER_FREQ_KEY, + ) + freq_hbox.AddSpacer(5) + + try: # range.start() == range.stop() in file= mode + + forms.slider( + parent=self.panel, sizer=freq_hbox, + proportion=3, + ps=self, + key=CENTER_FREQ_KEY, + minimum=self[FREQ_RANGE_KEY].start(), + maximum=self[FREQ_RANGE_KEY].stop(), + num_steps=1000, + ) + freq_hbox.AddSpacer(3) - 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) + except AssertionError: + pass - 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) + if self[FREQ_CORR_KEY] != None: # show frequency correction scrollbar + + corr_hbox.AddSpacer(3) + forms.text_box( + parent=self.panel, sizer=corr_hbox, + label='Freq. Correction (ppm)', + proportion=1, + converter=forms.float_converter(), + ps=self, + key=FREQ_CORR_KEY, + ) + corr_hbox.AddSpacer(5) + + forms.slider( + parent=self.panel, sizer=corr_hbox, + proportion=3, + ps=self, + key=FREQ_CORR_KEY, + minimum=-100, + maximum=+100, + num_steps=2010, + step_size=0.1, + ) + corr_hbox.AddSpacer(3) ################################################## # Gain controls ################################################## - 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]) + gc_vbox = forms.static_box_sizer(parent=self.panel, + label="Gain Settings", + orient=wx.VERTICAL, + bold=True) + gc_vbox.AddSpacer(3) + + # Add gain controls to top window sizer + vbox.Add(gc_vbox, 0, wx.EXPAND) + vbox.AddSpacer(5) + + for gain_name in self.get_gain_names(): + range = self[GAIN_RANGE_KEY(gain_name)] + gain = self[GAIN_KEY(gain_name)] + + #print gain_name, gain, range.to_pp_string() + if range.start() < range.stop(): + gain_hbox = wx.BoxSizer(wx.HORIZONTAL) + gc_vbox.Add(gain_hbox, 0, wx.EXPAND) + gc_vbox.AddSpacer(3) + + gain_hbox.AddSpacer(3) + forms.text_box( + parent=self.panel, sizer=gain_hbox, + proportion=1, + converter=forms.float_converter(), + ps=self, + key=GAIN_KEY(gain_name), + label=gain_name + " Gain (dB)", + ) + gain_hbox.AddSpacer(5) + forms.slider( + parent=self.panel, sizer=gain_hbox, + proportion=3, + ps=self, + key=GAIN_KEY(gain_name), + minimum=range.start(), + maximum=range.stop(), + step_size=range.step() or (range.stop() - range.start())/10, + ) + gain_hbox.AddSpacer(3) ################################################## # Bandwidth controls ################################################## - if self.bandwidth_ok: - self._add_section("Bandwidth", self.top_layout) + try: + + bw_range = self[BWIDTH_RANGE_KEY] + #print bw_range.to_pp_string() + if bw_range.start() < bw_range.stop(): + bwidth_vbox = forms.static_box_sizer(parent=self.panel, + label="Bandwidth", + orient=wx.VERTICAL, + bold=True) + bwidth_vbox.AddSpacer(3) + bwidth_hbox = wx.BoxSizer(wx.HORIZONTAL) + bwidth_vbox.Add(bwidth_hbox, 0, wx.EXPAND) + bwidth_vbox.AddSpacer(3) + + vbox.Add(bwidth_vbox, 0, wx.EXPAND) + vbox.AddSpacer(5) + + bwidth_hbox.AddSpacer(3) + forms.text_box( + parent=self.panel, sizer=bwidth_hbox, + proportion=1, + converter=forms.float_converter(), + ps=self, + key=BWIDTH_KEY, + label="Bandwidth (Hz)", + ) + bwidth_hbox.AddSpacer(5) + forms.slider( + parent=self.panel, sizer=bwidth_hbox, + proportion=3, + ps=self, + key=BWIDTH_KEY, + minimum=bw_range.start(), + maximum=bw_range.stop(), + step_size=bw_range.step() or (bw_range.stop() - bw_range.start())/100, + ) + bwidth_hbox.AddSpacer(3) + + except RuntimeError: + pass - 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) ################################################## # Sample rate controls ################################################## - self._add_section("Sample Rate", self.top_layout) + sr_vbox = forms.static_box_sizer(parent=self.panel, + label="Sample Rate", + orient=wx.VERTICAL, + bold=True) + sr_vbox.AddSpacer(3) + # First row of sample rate controls + sr_hbox = wx.BoxSizer(wx.HORIZONTAL) + sr_vbox.Add(sr_hbox, 0, wx.EXPAND) + sr_vbox.AddSpacer(5) + + # Add sample rate controls to top window sizer + vbox.Add(sr_vbox, 0, wx.EXPAND) + vbox.AddSpacer(5) + + sr_hbox.AddSpacer(3) + self.sample_rate_text = forms.text_box( + parent=self.panel, sizer=sr_hbox, + label='Sample Rate (Hz)', + proportion=1, + converter=forms.float_converter(), + ps=self, + key=SAMP_RATE_KEY, + ) + sr_hbox.AddSpacer(5) + + #forms.slider( + # parent=self.panel, sizer=sr_hbox, + # proportion=3, + # ps=self, + # key=SAMP_RATE_KEY, + # minimum=self[SAMP_RANGE_KEY].start(), + # maximum=self[SAMP_RANGE_KEY].stop(), + # step_size=self[SAMP_RANGE_KEY].step(), + #) + #sr_hbox.AddSpacer(3) - r = self.src.get_sample_rates() - self._srr = Range(r.start(), r.stop(), r.step() or (r.stop() - r.start())/100, self.src.get_sample_rate(), 100) - 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) ################################################## # File recording controls ################################################## - 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') + rec_vbox = forms.static_box_sizer(parent=self.panel, + label="File recording", + orient=wx.VERTICAL, + bold=True) + rec_vbox.AddSpacer(3) + # First row of sample rate controls + rec_hbox = wx.BoxSizer(wx.HORIZONTAL) + rec_vbox.Add(rec_hbox, 0, wx.EXPAND) + rec_vbox.AddSpacer(5) + + # Add sample rate controls to top window sizer + vbox.Add(rec_vbox, 0, wx.EXPAND) + vbox.AddSpacer(5) + + rec_hbox.AddSpacer(3) + self.record_text = forms.text_box( + parent=self.panel, sizer=rec_hbox, + label='File Name', + proportion=1, + ps=self, + key='record', + converter=forms.str_converter(), + ) + rec_hbox.AddSpacer(5) + + def record_callback(value): + if value: + self.sample_rate_text.Disable() + self.record_text.Disable() self.rec_file_name = self.record_to_filename() - print("Recording samples to ", self.rec_file_name) + print "Recording samples to ", self.rec_file_name self.file_sink.open(self.rec_file_name); else: - self._srw.setDisabled(False) - self._fre.setDisabled(False) - self._frb.setText('REC') + self.sample_rate_text.Enable() + self.record_text.Enable() self.file_sink.close() - print("Finished recording to", self.rec_file_name) + print "Finished recording to", self.rec_file_name - self._fre.returnPressed.connect(record_callback) - self._frb.clicked.connect(record_callback) + forms.toggle_button( + sizer=rec_hbox, + parent=self.panel, + false_label='REC', + true_label='STOP', + value=False, + callback=record_callback, + ) ################################################## # DC Offset controls ################################################## if self.dc_offset_mode != None: - self._add_section("DC Offset Correction", self.top_layout) - - 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) + dc_offset_vbox = forms.static_box_sizer(parent=self.panel, + label="DC Offset Correction", + orient=wx.VERTICAL, + bold=True) + dc_offset_vbox.AddSpacer(3) + # First row of sample rate controls + dc_offset_hbox = wx.BoxSizer(wx.HORIZONTAL) + dc_offset_vbox.Add(dc_offset_hbox, 0, wx.EXPAND) + dc_offset_vbox.AddSpacer(3) + + # Add frequency controls to top window sizer + vbox.Add(dc_offset_vbox, 0, wx.EXPAND) + vbox.AddSpacer(3) + + self.dc_offset_mode_chooser = forms.radio_buttons( + parent=self.panel, + value=self.dc_offset_mode, + callback=self.set_dc_offset_mode, + label='', + choices=[0, 1, 2], + labels=["Off", "Manual", "Auto"], + style=wx.RA_HORIZONTAL, + ) + dc_offset_hbox.Add(self.dc_offset_mode_chooser) + dc_offset_hbox.AddSpacer(3) + + dc_offset_hbox.AddSpacer(3) + self.dc_offset_real_text = forms.text_box( + parent=self.panel, sizer=dc_offset_hbox, + label='Real', + proportion=1, + converter=forms.float_converter(), + ps=self, + key='dc_offset_real', + ) + dc_offset_hbox.AddSpacer(3) + + self.dc_offset_real_slider = forms.slider( + parent=self.panel, sizer=dc_offset_hbox, + proportion=3, + minimum=-1, + maximum=+1, + step_size=0.001, + ps=self, + key='dc_offset_real', + ) + dc_offset_hbox.AddSpacer(3) + + dc_offset_hbox.AddSpacer(3) + self.dc_offset_imag_text = forms.text_box( + parent=self.panel, sizer=dc_offset_hbox, + label='Imag', + proportion=1, + converter=forms.float_converter(), + ps=self, + key='dc_offset_imag', + ) + dc_offset_hbox.AddSpacer(3) + + self.dc_offset_imag_slider = forms.slider( + parent=self.panel, sizer=dc_offset_hbox, + proportion=3, + minimum=-1, + maximum=+1, + step_size=0.001, + ps=self, + key='dc_offset_imag', + ) + dc_offset_hbox.AddSpacer(3) ################################################## # IQ Imbalance controls ################################################## if self.iq_balance_mode != None: - self._add_section("IQ Imbalance Correction", self.top_layout) - - 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) + iq_balance_vbox = forms.static_box_sizer(parent=self.panel, + label="IQ Imbalance Correction", + orient=wx.VERTICAL, + bold=True) + iq_balance_vbox.AddSpacer(3) + # First row of sample rate controls + iq_balance_hbox = wx.BoxSizer(wx.HORIZONTAL) + iq_balance_vbox.Add(iq_balance_hbox, 0, wx.EXPAND) + iq_balance_vbox.AddSpacer(3) + + # Add frequency controls to top window sizer + vbox.Add(iq_balance_vbox, 0, wx.EXPAND) + vbox.AddSpacer(3) + + self.iq_balance_mode_chooser = forms.radio_buttons( + parent=self.panel, + value=self.iq_balance_mode, + callback=self.set_iq_balance_mode, + label='', + choices=[0, 1, 2], + labels=["Off", "Manual", "Auto"], + style=wx.RA_HORIZONTAL, + ) + iq_balance_hbox.Add(self.iq_balance_mode_chooser) + iq_balance_hbox.AddSpacer(3) + + iq_balance_hbox.AddSpacer(3) + self.iq_balance_mag_text = forms.text_box( + parent=self.panel, sizer=iq_balance_hbox, + label='Mag', + proportion=1, + converter=forms.float_converter(), + ps=self, + key='iq_balance_mag', + ) + iq_balance_hbox.AddSpacer(3) + + self.iq_balance_mag_slider = forms.slider( + parent=self.panel, sizer=iq_balance_hbox, + proportion=3, + minimum=-1, + maximum=+1, + step_size=0.001, + ps=self, + key='iq_balance_mag', + ) + iq_balance_hbox.AddSpacer(3) + + iq_balance_hbox.AddSpacer(3) + self.iq_balance_pha_text = forms.text_box( + parent=self.panel, sizer=iq_balance_hbox, + label='Phase', + proportion=1, + converter=forms.float_converter(), + ps=self, + key='iq_balance_pha', + ) + iq_balance_hbox.AddSpacer(3) + + self.iq_balance_pha_slider = forms.slider( + parent=self.panel, sizer=iq_balance_hbox, + proportion=3, + minimum=-1, + maximum=+1, + step_size=0.001, + ps=self, + key='iq_balance_pha', + ) + iq_balance_hbox.AddSpacer(3) def set_dc_offset_mode(self, dc_offset_mode): if dc_offset_mode == 1: - self._dcrw.setDisabled(False) - self._dciw.setDisabled(False) + self.dc_offset_real_text.Enable() + self.dc_offset_real_slider.Enable() + self.dc_offset_imag_text.Enable() + self.dc_offset_imag_slider.Enable() - self.set_dc_offset() + self.set_dc_offset(0) else: - self._dcrw.setDisabled(True) - self._dciw.setDisabled(True) + self.dc_offset_real_text.Disable() + self.dc_offset_real_slider.Disable() + self.dc_offset_imag_text.Disable() + self.dc_offset_imag_slider.Disable() self.dc_offset_mode = dc_offset_mode self.src.set_dc_offset_mode(dc_offset_mode) + self.dc_offset_mode_chooser.set_value(self.dc_offset_mode) - 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) + def set_dc_offset(self, value): + correction = complex( self['dc_offset_real'], self['dc_offset_imag'] ) try: - self.src.set_dc_offset(correction) + self.src.set_dc_offset( correction ) if self._verbose: - print("Set DC offset to", correction) + print "Set DC offset to", correction except RuntimeError as ex: - print(ex) + print ex def set_iq_balance_mode(self, iq_balance_mode): if iq_balance_mode == 1: - self._iqpw.setDisabled(False) - self._iqmw.setDisabled(False) + self.iq_balance_mag_text.Enable() + self.iq_balance_mag_slider.Enable() + self.iq_balance_pha_text.Enable() + self.iq_balance_pha_slider.Enable() - self.set_iq_balance() + self.set_iq_balance(0) else: - self._iqpw.setDisabled(True) - self._iqmw.setDisabled(True) + self.iq_balance_mag_text.Disable() + self.iq_balance_mag_slider.Disable() + self.iq_balance_pha_text.Disable() + self.iq_balance_pha_slider.Disable() self.iq_balance_mode = iq_balance_mode self.src.set_iq_balance_mode(iq_balance_mode) + self.iq_balance_mode_chooser.set_value(self.iq_balance_mode) - 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) + def set_iq_balance(self, value): + correction = complex( self['iq_balance_mag'], self['iq_balance_pha'] ) try: - self.src.set_iq_balance(correction) + self.src.set_iq_balance( correction ) if self._verbose: - print("Set IQ balance to", correction) + print "Set IQ balance to", correction except RuntimeError as ex: - print(ex) + print ex def set_sample_rate(self, samp_rate): samp_rate = self.src.set_sample_rate(samp_rate) - if hasattr(self.scope, 'set_frequency_range'): - self.scope.set_frequency_range(self.src.get_center_freq(), samp_rate) if hasattr(self.scope, 'set_sample_rate'): self.scope.set_sample_rate(samp_rate) if self._verbose: - print("Set sample rate to:", samp_rate) + print "Set sample rate to:", samp_rate try: - 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): + self[BWIDTH_KEY] = self.set_bandwidth(samp_rate) + except RuntimeError: pass return samp_rate + def get_gain_names(self): + return self.src.get_gain_names() + def set_named_gain(self, gain, name): - if self._verbose: - print("Trying to set " + name + " gain to:", gain) + if gain is None: + g = self[GAIN_RANGE_KEY(name)] + gain = float(g.start()+g.stop())/2 + if self._verbose: + print "Using auto-calculated mid-point gain" + self[GAIN_KEY(name)] = gain + return gain = self.src.set_gain(gain, name) if self._verbose: - print("Set " + name + " gain to:", gain) + print "Set " + name + " gain to:", gain def set_bandwidth(self, bw): - 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) + clipped_bw = self[BWIDTH_RANGE_KEY].clip(bw) if self.src.get_bandwidth() != clipped_bw: bw = self.src.set_bandwidth(clipped_bw) if self._verbose: - print("Set bandwidth to:", bw) + print "Set bandwidth to:", bw return bw + def set_freq_from_callback(self, freq): + freq = self.src.set_center_freq(freq) + self[CENTER_FREQ_KEY] = freq; + def set_freq(self, freq): + if freq is None: + f = self[FREQ_RANGE_KEY] + freq = float(f.start()+f.stop())/2.0 + if self._verbose: + print "Using auto-calculated mid-point frequency" + self[CENTER_FREQ_KEY] = freq + return freq = self.src.set_center_freq(freq) - if hasattr(self.scope, 'set_frequency_range'): - self.scope.set_frequency_range(freq, self.src.get_sample_rate()) if hasattr(self.scope, 'set_baseband_freq'): self.scope.set_baseband_freq(freq) - 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 - if freq is not None: if self._verbose: - print("Set center frequency to %.10g"%freq) + print "Set center frequency to", freq elif self._verbose: - print("Failed to set freq.") + print "Failed to set freq." return freq def set_freq_corr(self, ppm): - self.ppm = self.src.set_freq_corr(ppm) - if self._verbose: - 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) + if ppm is None: + ppm = 0.0 + if self._verbose: + print "Using frequency corrrection of", ppm + self[FREQ_CORR_KEY] = ppm + return - def quitting(): - tb.stop() - tb.wait() - qapp.aboutToQuit.connect(quitting) - qapp.exec_() + ppm = self.src.set_freq_corr(ppm) + if self._verbose: + print "Set frequency correction to:", ppm +def main (): + app = stdgui2.stdapp(app_top_block, "osmocom Spectrum Browser", nstatus=1) + app.MainLoop() if __name__ == '__main__': - main() + main () |