1#!/usr/bin/env python3
2#
3# Copyright 2012 Free Software Foundation, Inc.
4#
5# This file is part of GNU Radio
6#
7# GNU Radio is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 3, or (at your option)
10# any later version.
11#
12# GNU Radio is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with GNU Radio; see the file COPYING.  If not, write to
19# the Free Software Foundation, Inc., 51 Franklin Street,
20# Boston, MA 02110-1301, USA.
21#
22
23import osmosdr
24from gnuradio import blocks
25from gnuradio import gr
26from gnuradio import eng_notation
27from gnuradio.filter import firdes
28from gnuradio.eng_option import eng_option
29from optparse import OptionParser
30from functools import partial
31
32import sys
33import signal
34import time
35import datetime
36
37try:
38    from PyQt5 import Qt
39    from gnuradio import qtgui
40    import sip
41    from gnuradio.qtgui import Range, RangeWidget
42except ImportError:
43    sys.stderr.write("Error importing GNU Radio's Qtgui.\n")
44    sys.exit(1)
45
46
47class CallEvent(Qt.QEvent):
48    """An event containing a request for a function call."""
49    EVENT_TYPE = Qt.QEvent.Type(Qt.QEvent.registerEventType())
50
51    def __init__(self, fn, *args, **kwargs):
52        Qt.QEvent.__init__(self, self.EVENT_TYPE)
53        self.fn = fn
54        self.args = args
55        self.kwargs = kwargs
56
57
58class freq_recv(gr.sync_block, Qt.QObject):
59    def __init__(self, callback):
60        gr.sync_block.__init__(self, name="freq_recv", in_sig=None, out_sig=None)
61        Qt.QObject.__init__(self)
62
63        self.set_freq=callback
64
65        # Advertise 'msg' port
66        self.message_port_register_in(gr.pmt.intern('msg'))
67        self.set_msg_handler(gr.pmt.intern('msg'), self.handle_msg)
68
69    def handle_msg(self, msg_pmt):
70        # Unpack message & call set_freq on main thread
71        meta = gr.pmt.to_python(gr.pmt.car(msg_pmt))
72        msg = gr.pmt.cdr(msg_pmt)
73        if meta=="freq":
74            freq = gr.pmt.to_double(msg)
75            Qt.QCoreApplication.postEvent(self, CallEvent(self.set_freq, freq))
76
77    def event(self, event):
78        event.accept()
79        result = event.fn(*event.args, **event.kwargs)
80        return True
81
82
83class app_top_block(gr.top_block, Qt.QMainWindow):
84    def __init__(self, argv, title):
85        gr.top_block.__init__(self, title)
86        Qt.QMainWindow.__init__(self)
87        self.setWindowTitle(title)
88
89        parser = OptionParser(option_class=eng_option)
90        parser.add_option("-a", "--args", type="string", default="",
91                          help="Device args, [default=%default]")
92        parser.add_option("-A", "--antenna", type="string", default=None,
93                          help="Select RX antenna where appropriate")
94        parser.add_option("", "--clock-source",
95                          help="Set the clock source; typically 'internal', 'external', 'external_1pps', 'mimo' or 'gpsdo'")
96        parser.add_option("-s", "--samp-rate", type="eng_float", default=None,
97                          help="Set sample rate (bandwidth), minimum by default")
98        parser.add_option("-f", "--center-freq", type="eng_float", default=None,
99                          help="Set frequency to FREQ", metavar="FREQ")
100        parser.add_option("-c", "--freq-corr", type="eng_float", default=None,
101                          help="Set frequency correction (ppm)")
102        parser.add_option("-g", "--gain", type="eng_float", default=None,
103                          help="Set gain in dB (default is midpoint)")
104        parser.add_option("-G", "--gains", type="string", default=None,
105                          help="Set named gain in dB, name:gain,name:gain,...")
106        parser.add_option("-r", "--record", type="string", default="/tmp/name-f%F-s%S-t%T.cfile",
107                          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")
108        parser.add_option("", "--dc-offset-mode", type="int", default=None,
109                          help="Set the RX frontend DC offset correction mode")
110        parser.add_option("", "--iq-balance-mode", type="int", default=None,
111                          help="Set the RX frontend IQ imbalance correction mode")
112        parser.add_option("-W", "--waterfall", action="store_true", default=False,
113                          help="Enable waterfall display")
114        parser.add_option("-F", "--fosphor", action="store_true", default=False,
115                          help="Enable fosphor display")
116        parser.add_option("-S", "--oscilloscope", action="store_true", default=False,
117                          help="Enable oscilloscope display")
118        parser.add_option("-Q", "--qtgui", action="store_true", default=False,
119                          help="Enable QTgui 'all-in-one' display")
120        parser.add_option("", "--avg-alpha", type="eng_float", default=1e-1,
121                          help="Set fftsink averaging factor, default=[%default]")
122        parser.add_option("", "--averaging", action="store_true", default=False,
123                          help="Enable fftsink averaging, default=[%default]")
124        parser.add_option("", "--peak-hold", action="store_true", default=False,
125                          help="Enable fftsink peak hold, default=[%default]")
126        parser.add_option("", "--ref-scale", type="eng_float", default=1.0,
127                          help="Set dBFS=0dB input value, default=[%default]")
128        parser.add_option("", "--fft-size", type="int", default=1024,
129                          help="Set number of FFT bins [default=%default]")
130        parser.add_option("", "--fft-rate", type="int", default=30,
131                          help="Set FFT update rate, [default=%default]")
132        parser.add_option("-v", "--verbose", action="store_true", default=False,
133                          help="Use verbose console output [default=%default]")
134
135        (options, args) = parser.parse_args()
136        if len(args) != 0:
137            parser.print_help()
138            sys.exit(1)
139        self.options = options
140
141        self._verbose = options.verbose
142
143        try:
144            self.src = osmosdr.source(options.args)
145        except RuntimeError:
146            print("Couldn't instanciate source (no device present?).", file=sys.stderr)
147            sys.exit(1)
148
149        try:
150            self.src.get_sample_rates().start()
151        except RuntimeError:
152            print("Source has no sample rates (wrong device arguments?).", file=sys.stderr)
153            sys.exit(1)
154
155        # Set the antenna
156        if options.antenna:
157            self.src.set_antenna(options.antenna)
158
159        # Set the clock source:
160        if options.clock_source is not None:
161            self.src.set_clock_source(options.clock_source)
162
163        if options.samp_rate is None:
164            options.samp_rate = self.src.get_sample_rates().start()
165
166        if options.gain is None:
167            gain = self.src.get_gain()
168            if gain is None:
169                # if no gain was specified, use the mid-point in dB
170                r = self.src.get_gain_range()
171                try: # empty gain range returned in file= mode
172                    options.gain = float(r.start()+r.stop())/2
173                except RuntimeError:
174                    options.gain = 0
175            else:
176                options.gain = gain
177
178        self.src.set_gain(options.gain)
179
180        if self._verbose:
181            gain_names = self.src.get_gain_names()
182            for name in gain_names:
183                rg = self.src.get_gain_range(name)
184                print("%s gain range: start %g stop %g step %g" % (name, rg.start(), rg.stop(), rg.step()))
185
186        if options.gains:
187            for tuple in options.gains.split(","):
188                name, gain = tuple.split(":")
189                gain = int(gain)
190                print("Setting gain %s to %g." % (name, gain))
191                self.src.set_gain(gain, name)
192
193        if self._verbose:
194            rates = self.src.get_sample_rates()
195            print('Supported sample rates %.10g-%.10g step %.10g.' % (rates.start(), rates.stop(), rates.step()))
196
197        self.bandwidth_ok = True
198        try:
199            rg = self.src.get_bandwidth_range()
200            range_start = rg.start()
201            if self._verbose:
202                print('Supported bandwidth rates %.10g-%.10g step %.10g.' % (rg.start(), rg.stop(), rg.step()))
203        except RuntimeError as ex:
204            self.bandwidth_ok = False
205
206        if options.center_freq is None:
207            freq = self.src.get_center_freq()
208            if freq != 0:
209                options.center_freq = freq
210            else:
211                # if no freq was specified, use the mid-point in Hz
212                r = self.src.get_freq_range()
213                options.center_freq = float(r.start()+r.stop())/2
214                if self._verbose:
215                    print("Using auto-calculated mid-point frequency")
216
217        input_rate = self.src.set_sample_rate(options.samp_rate)
218        self.src.set_bandwidth(input_rate)
219
220        if self._verbose:
221            ranges = self.src.get_freq_range()
222            print("Supported frequencies %s-%s"%(eng_notation.num_to_str(ranges.start()), eng_notation.num_to_str(ranges.stop())))
223
224
225            for name in self.src.get_gain_names():
226                print("GAIN(%s): %g"%(name, self.src.get_gain(name)))
227
228        # initialize values from options
229        if options.freq_corr is not None:
230            self.set_freq_corr(options.freq_corr)
231
232        self.dc_offset_mode = options.dc_offset_mode
233        self.iq_balance_mode = options.iq_balance_mode
234
235        # initialize reasonable defaults for DC / IQ correction
236        self.dc_offset_real = 0
237        self.dc_offset_imag = 0
238        self.iq_balance_mag = 0
239        self.iq_balance_pha = 0
240
241        if options.fosphor:
242            from gnuradio import fosphor
243            self.scope = fosphor.qt_sink_c()
244            self.scope.set_frequency_range(0, input_rate)
245            self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
246            self.scope_win.setMinimumSize(800, 300)
247        elif options.waterfall:
248            self.scope = qtgui.waterfall_sink_c(
249                options.fft_size,
250                wintype=firdes.WIN_BLACKMAN_hARRIS,
251                fc=0,
252                bw=input_rate,
253                name="",
254                nconnections=1
255            )
256            self.scope.enable_grid(False)
257            self.scope.enable_axis_labels(True)
258            self.scope.set_intensity_range(-100, 20)
259            self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
260            self.scope_win.setMinimumSize(800, 420)
261
262        elif options.oscilloscope:
263            self.scope = qtgui.time_sink_c(
264                options.fft_size,
265                samp_rate=input_rate,
266                name="",
267                nconnections=1
268            )
269            self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
270            self.scope_win.setMinimumSize(800, 600)
271
272        elif options.qtgui:
273            self.scope = qtgui.sink_c(
274                options.fft_size,
275                wintype=firdes.WIN_BLACKMAN_hARRIS,
276                fc=0,
277                bw=input_rate,
278                name="",
279                plotfreq=True,
280                plotwaterfall=True,
281                plottime=True,
282                plotconst=True
283            )
284            self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
285            self.scope.set_update_time(1.0/10)
286            self.scope_win.setMinimumSize(800, 600)
287
288        else:
289            self.scope = qtgui.freq_sink_c(
290                fftsize=options.fft_size,
291                wintype=firdes.WIN_BLACKMAN_hARRIS,
292                fc=0,
293                bw=input_rate,
294                name="",
295                nconnections=1
296            )
297            self.scope_win = sip.wrapinstance(self.scope.pyqwidget(), Qt.QWidget)
298            self.scope.disable_legend()
299            self.scope_win.setMinimumSize(800, 420)
300
301        self.connect((self.src, 0), (self.scope, 0))
302        try:
303            self.freq = freq_recv(self.set_freq)
304            self.msg_connect((self.scope, 'freq'), (self.freq, 'msg'))
305        except RuntimeError:
306            self.freq = None
307
308        self.file_sink = blocks.file_sink(gr.sizeof_gr_complex, "/dev/null", False)
309        self.file_sink.set_unbuffered(False)
310        self.file_sink.close() # close the sink immediately
311        # lock/connect/unlock at record button event did not work, so we leave it connected at all times
312        self.connect(self.src, self.file_sink)
313
314        self._build_gui()
315
316        # set initial values
317        if not self.set_freq(options.center_freq):
318            self._set_status_msg("Failed to set initial frequency")
319        if options.record is not None:
320            self._fre.insert(options.record)
321
322    def record_to_filename(self):
323        s = self._fre.text()
324        s = s.replace('%S', '%e' % self.src.get_sample_rate())
325        s = s.replace('%F', '%e' % self.src.get_center_freq())
326        s = s.replace('%T', datetime.datetime.now().strftime('%Y%m%d%H%M%S'))
327        return s
328
329    def _set_status_msg(self, msg, timeout=0):
330        self.status.showMessage(msg, timeout)
331
332    def _shrink(self, widget):
333        """Try to shrink RangeWidget by removing unnecessary margins"""
334        try:
335            widget.layout().setContentsMargins(0, 0, 0, 0)
336            widget.children()[0].layout().setContentsMargins(0, 0, 0, 0)
337        except:
338            pass
339
340    def _add_section(self, text, layout):
341        """Add a section header to the GUI"""
342        frame = Qt.QWidget()
343        frame_layout = Qt.QHBoxLayout()
344        frame_layout.setContentsMargins(0, 0, 0, 0)
345        frame.setLayout(frame_layout)
346
347        wid = Qt.QLabel()
348        wid.setText(text)
349        wid.setStyleSheet("font-weight: bold;")
350        frame_layout.addWidget(wid)
351        wid = Qt.QFrame()
352        wid.setFrameShape(Qt.QFrame.HLine)
353        frame_layout.addWidget(wid)
354        frame_layout.setStretchFactor(wid, 1)
355
356        layout.addWidget(frame)
357
358    def _chooser(self, names, callback, default=0):
359        """A simple radio-button chooser"""
360        buttons = Qt.QWidget()
361        blayout = Qt.QHBoxLayout()
362        bgroup = Qt.QButtonGroup()
363        buttons.setObjectName("foo")
364        buttons.setStyleSheet("QWidget#foo {border: 1px outset grey;}")
365        buttons.setLayout(blayout)
366        chooser = []
367        for (num, txt) in enumerate(names):
368            rb = Qt.QRadioButton(txt)
369            rb.clicked.connect(partial(callback, num))
370            chooser.append(rb)
371            bgroup.addButton(rb,num)
372            blayout.addWidget(rb)
373            if num == default:
374                rb.setChecked(True)
375        return buttons
376
377    def _build_gui(self):
378
379        self.top_widget = Qt.QWidget()
380
381        self.top_layout = Qt.QVBoxLayout(self.top_widget)
382        self.top_layout.addWidget(self.scope_win)
383
384        self.setCentralWidget(self.top_widget)
385
386        self.status = Qt.QStatusBar()
387        self.setStatusBar(self.status)
388        self.status.setStyleSheet("QStatusBar{border-top: 1px outset grey;}")
389
390        if hasattr(RangeWidget, 'EngSlider'):
391            eng_widget="eng_slider"
392        else:
393            eng_widget="counter_slider"
394
395        ##################################################
396        # Frequency controls
397        ##################################################
398        self._add_section("Frequency", self.top_layout)
399
400        r = self.src.get_freq_range()
401        self._fr = Range(r.start(), r.stop(), (r.start()+r.stop())/100, self.src.get_center_freq(), 200)
402        self._fw = RangeWidget(self._fr, self.set_freq, 'Center Frequency (Hz)', eng_widget, float)
403        self._shrink(self._fw)
404        self.top_layout.addWidget(self._fw)
405
406        if hasattr(self, 'ppm') and self.ppm is not None:
407            self._fcr = Range(-100, 100, 0.1, self.src.get_freq_corr(), 200)
408            self._fcw = RangeWidget(self._fcr, self.set_freq_corr, 'Freq. Correction (ppm)', "counter_slider", float)
409            self._shrink(self._fcw)
410            self.top_layout.addWidget(self._fcw)
411
412        ##################################################
413        # Gain controls
414        ##################################################
415        self._add_section("Gains", self.top_layout)
416
417        self._gr={}
418        self._gw={}
419        for gain_name in self.src.get_gain_names():
420            rg = self.src.get_gain_range(gain_name)
421            self._gr[gain_name] = Range(rg.start(), rg.stop(), rg.step(), self.src.get_gain(gain_name), 100)
422            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)
423            self._shrink(self._gw[gain_name])
424            self._gw[gain_name].d_widget.counter.setDecimals(2)
425            self.top_layout.addWidget(self._gw[gain_name])
426
427        ##################################################
428        # Bandwidth controls
429        ##################################################
430        if self.bandwidth_ok:
431            self._add_section("Bandwidth", self.top_layout)
432
433            r = self.src.get_bandwidth_range()
434            self._bwr = Range(r.start(), r.stop(), r.step() or (r.stop() - r.start())/100, self.src.get_bandwidth(), 100)
435            self._bww = RangeWidget(self._bwr, self.set_bandwidth, 'Bandwidth (Hz):', eng_widget, float)
436            self._shrink(self._bww)
437            self.top_layout.addWidget(self._bww)
438
439        ##################################################
440        # Sample rate controls
441        ##################################################
442        self._add_section("Sample Rate", self.top_layout)
443
444        r = self.src.get_sample_rates()
445        self._srr = Range(r.start(), r.stop(), r.step() or (r.stop() - r.start())/100, self.src.get_sample_rate(), 100)
446        self._srw = RangeWidget(self._srr, self.set_sample_rate, 'Sample Rate (Hz)', eng_widget, float)
447        self._shrink(self._srw)
448        self.top_layout.addWidget(self._srw)
449
450        ##################################################
451        # File recording controls
452        ##################################################
453
454        self._add_section("File recording", self.top_layout)
455
456        wid = Qt.QWidget()
457
458        layout = Qt.QHBoxLayout()
459        layout.setContentsMargins(0, 0, 0, 0)
460
461        self._frl = Qt.QLabel('File Name')
462        layout.addWidget(self._frl)
463
464        self._fre = Qt.QLineEdit()
465        layout.addWidget(self._fre)
466
467        self._frb = Qt.QPushButton('REC')
468        layout.addWidget(self._frb)
469
470        wid.setLayout(layout)
471        self.top_layout.addWidget(wid)
472
473        self.recording = 0
474        def record_callback():
475            self.recording = 1-self.recording
476            if self.recording:
477                self._srw.setDisabled(True)
478                self._fre.setDisabled(True)
479                self._frb.setText('STOP')
480
481                self.rec_file_name = self.record_to_filename()
482
483                print("Recording samples to ", self.rec_file_name)
484                self.file_sink.open(self.rec_file_name);
485            else:
486                self._srw.setDisabled(False)
487                self._fre.setDisabled(False)
488                self._frb.setText('REC')
489
490                self.file_sink.close()
491                print("Finished recording to", self.rec_file_name)
492
493        self._fre.returnPressed.connect(record_callback)
494        self._frb.clicked.connect(record_callback)
495
496        ##################################################
497        # DC Offset controls
498        ##################################################
499
500        if self.dc_offset_mode != None:
501            self._add_section("DC Offset Correction", self.top_layout)
502
503            wid = Qt.QWidget()
504
505            layout = Qt.QHBoxLayout()
506            layout.setContentsMargins(0, 0, 0, 0)
507
508            self._dcb = self._chooser(["Off", "Manual", "Auto"], self.set_dc_offset_mode, self.dc_offset_mode)
509            layout.addWidget(self._dcb)
510
511            self._dcrr = Range(-1, +1, 0.001, 0, 20)
512            self._dcrw = RangeWidget(self._dcrr, self.set_dc_offset_real, 'Real', "counter_slider", float)
513            self._shrink(self._dcrw)
514            layout.addWidget(self._dcrw)
515
516            self._dcir = Range(-1, +1, 0.001, 0, 20)
517            self._dciw = RangeWidget(self._dcrr, self.set_dc_offset_imag, 'Imag', "counter_slider", float)
518            self._shrink(self._dciw)
519            layout.addWidget(self._dciw)
520
521            wid.setLayout(layout)
522            self.top_layout.addWidget(wid)
523
524        ##################################################
525        # IQ Imbalance controls
526        ##################################################
527
528        if self.iq_balance_mode != None:
529            self._add_section("IQ Imbalance Correction", self.top_layout)
530
531            wid = Qt.QWidget()
532
533            layout = Qt.QHBoxLayout()
534            layout.setContentsMargins(0, 0, 0, 0)
535
536            self._iqb = self._chooser(["Off", "Manual", "Auto"], self.set_dc_offset_mode, self.iq_balance_mode)
537            layout.addWidget(self._iqb)
538
539            self._iqmr = Range(-1, +1, 0.001, 0, 20)
540            self._iqmw = RangeWidget(self._iqmr, self.set_iq_balance_mag, 'Mag', "counter_slider", float)
541            self._shrink(self._iqmw)
542            layout.addWidget(self._iqmw)
543
544            self._iqpr = Range(-1, +1, 0.001, 0, 20)
545            self._iqpw = RangeWidget(self._iqpr, self.set_iq_balance_pha, 'Pha', "counter_slider", float)
546            self._shrink(self._iqpw)
547            layout.addWidget(self._iqpw)
548
549            wid.setLayout(layout)
550            self.top_layout.addWidget(wid)
551
552    def set_dc_offset_mode(self, dc_offset_mode):
553        if dc_offset_mode == 1:
554            self._dcrw.setDisabled(False)
555            self._dciw.setDisabled(False)
556
557            self.set_dc_offset()
558        else:
559            self._dcrw.setDisabled(True)
560            self._dciw.setDisabled(True)
561
562        self.dc_offset_mode = dc_offset_mode
563        self.src.set_dc_offset_mode(dc_offset_mode)
564
565    def set_dc_offset_real(self, value):
566        self.dc_offset_real = value
567        self.set_dc_offset()
568
569    def set_dc_offset_imag(self, value):
570        self.dc_offset_imag = value
571        self.set_dc_offset()
572
573    def set_dc_offset(self):
574        correction = complex(self.dc_offset_real, self.dc_offset_imag)
575
576        try:
577            self.src.set_dc_offset(correction)
578
579            if self._verbose:
580                print("Set DC offset to", correction)
581        except RuntimeError as ex:
582            print(ex)
583
584    def set_iq_balance_mode(self, iq_balance_mode):
585        if iq_balance_mode == 1:
586            self._iqpw.setDisabled(False)
587            self._iqmw.setDisabled(False)
588
589            self.set_iq_balance()
590        else:
591            self._iqpw.setDisabled(True)
592            self._iqmw.setDisabled(True)
593
594        self.iq_balance_mode = iq_balance_mode
595        self.src.set_iq_balance_mode(iq_balance_mode)
596
597    def set_iq_balance_mag(self, value):
598        self.iq_balance_mag = value
599        self.set_iq_balance()
600
601    def set_iq_balance_pha(self, value):
602        self.iq_balance_pha = value
603        self.set_iq_balance()
604
605    def set_iq_balance(self):
606        correction = complex(self.iq_balance_mag, self.iq_balance_pha)
607
608        try:
609            self.src.set_iq_balance(correction)
610
611            if self._verbose:
612                print("Set IQ balance to", correction)
613        except RuntimeError as ex:
614            print(ex)
615
616    def set_sample_rate(self, samp_rate):
617        samp_rate = self.src.set_sample_rate(samp_rate)
618        if hasattr(self.scope, 'set_frequency_range'):
619            self.scope.set_frequency_range(self.src.get_center_freq(), samp_rate)
620        if hasattr(self.scope, 'set_sample_rate'):
621            self.scope.set_sample_rate(samp_rate)
622        if self._verbose:
623            print("Set sample rate to:", samp_rate)
624
625        try:
626            if hasattr(self._bww.d_widget, 'setValue'):
627                self._bww.d_widget.setValue(samp_rate)
628            else:
629                self._bww.d_widget.counter.setValue(samp_rate)
630        except (RuntimeError, AttributeError):
631            pass
632
633        return samp_rate
634
635    def set_named_gain(self, gain, name):
636        if self._verbose:
637            print("Trying to set " + name + " gain to:", gain)
638
639        gain = self.src.set_gain(gain, name)
640        if self._verbose:
641            print("Set " + name + " gain to:", gain)
642
643    def set_bandwidth(self, bw):
644        if self._verbose:
645            print("Trying to set bandwidth to:", bw)
646        clipped_bw = self.src.get_bandwidth_range().clip(bw)
647        if self._verbose:
648            print("Clipping bandwidth to:", clipped_bw)
649        if self.src.get_bandwidth() != clipped_bw:
650            bw = self.src.set_bandwidth(clipped_bw)
651
652            if self._verbose:
653                print("Set bandwidth to:", bw)
654
655        return bw
656
657    def set_freq(self, freq):
658
659        freq = self.src.set_center_freq(freq)
660
661        if hasattr(self.scope, 'set_frequency_range'):
662            self.scope.set_frequency_range(freq, self.src.get_sample_rate())
663        if hasattr(self.scope, 'set_baseband_freq'):
664            self.scope.set_baseband_freq(freq)
665
666        try:
667            if hasattr(self._fw.d_widget, 'setValue'):
668                self._fw.d_widget.setValue(freq)
669            else:
670                self._fw.d_widget.counter.setValue(freq)
671        except (RuntimeError, AttributeError):
672            pass
673
674        if freq is not None:
675            if self._verbose:
676                print("Set center frequency to %.10g"%freq)
677        elif self._verbose:
678            print("Failed to set freq.")
679        return freq
680
681    def set_freq_corr(self, ppm):
682        self.ppm = self.src.set_freq_corr(ppm)
683        if self._verbose:
684            print("Set frequency correction to:", self.ppm)
685
686
687def main():
688    qapp = Qt.QApplication(sys.argv)
689
690    tb = app_top_block(qapp.arguments(), "osmocom Spectrum Browser")
691    tb.start()
692    tb.show()
693
694    def sig_handler(sig=None, frame=None):
695        print("caught signal")
696        Qt.QApplication.quit()
697
698    signal.signal(signal.SIGINT, sig_handler)
699    signal.signal(signal.SIGTERM, sig_handler)
700
701    # this timer is necessary for signals (^C) to work
702    timer = Qt.QTimer()
703    timer.start(500)
704    timer.timeout.connect(lambda: None)
705
706    def quitting():
707        tb.stop()
708        tb.wait()
709    qapp.aboutToQuit.connect(quitting)
710    qapp.exec_()
711
712
713if __name__ == '__main__':
714    main()
715