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