diff options
author | Dimitri Stolnikov <horiz0n@gmx.net> | 2013-04-28 12:41:35 +0200 |
---|---|---|
committer | Dimitri Stolnikov <horiz0n@gmx.net> | 2013-04-28 12:46:03 +0200 |
commit | a31ea525fb8439b3708b8f4124ac99994ff7a978 (patch) | |
tree | 4906b0256fb03e5a02dbb310b77d311027e11bae /apps | |
parent | a5bdb272402fb012c0a97556cf50a69128e4c573 (diff) |
apps: add spectrum browser and signal generator
usage examples:
osmocom_fft -a "rtl=0" -f 100e6 -s 2.4e6 -g 15
osmocom_siggen_gui -a "hackrf=0" -s 5e6 -f 100e6 --sine
osmocom_siggen_gui -a "hackrf=0" -s 5e6 -f 100e6 --sweep -x 2M -y 1 -c34
known issues:
- switching between siggen modes is broken at the moment (WIP) and has
to be made via cli switches only.
- filter bandwidth controls have no effect for TX (this has to be
investigated)
Diffstat (limited to 'apps')
-rw-r--r-- | apps/CMakeLists.txt | 11 | ||||
-rwxr-xr-x | apps/osmocom_fft | 270 | ||||
-rwxr-xr-x | apps/osmocom_siggen | 51 | ||||
-rw-r--r-- | apps/osmocom_siggen_base.py | 397 | ||||
-rwxr-xr-x | apps/osmocom_siggen_gui | 374 |
5 files changed, 1102 insertions, 1 deletions
diff --git a/apps/CMakeLists.txt b/apps/CMakeLists.txt index c837d77..88e5d4a 100644 --- a/apps/CMakeLists.txt +++ b/apps/CMakeLists.txt @@ -20,6 +20,15 @@ include(GrPython) GR_PYTHON_INSTALL( + FILES + osmocom_siggen_base.py + DESTINATION ${GR_PYTHON_DIR}/osmosdr +) + +GR_PYTHON_INSTALL( PROGRAMS - DESTINATION bin + osmocom_fft + osmocom_siggen + osmocom_siggen_gui + DESTINATION ${GR_RUNTIME_DIR} ) diff --git a/apps/osmocom_fft b/apps/osmocom_fft new file mode 100755 index 0000000..5449354 --- /dev/null +++ b/apps/osmocom_fft @@ -0,0 +1,270 @@ +#!/usr/bin/env python +# +# 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 +from gnuradio import gr, gru +from gnuradio import eng_notation +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +import sys +import numpy + +try: + 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 wxgui. Please make sure gr-wxgui is installed.\n") + sys.exit(1) + +class app_top_block(stdgui2.std_top_block): + def __init__(self, frame, panel, vbox, argv): + stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv) + + self.frame = frame + self.panel = panel + + parser = OptionParser(option_class=eng_option) + parser.add_option("-a", "--args", type="string", default="", + help="device args, [default=%default]") + parser.add_option("-A", "--antenna", type="string", default=None, + help="select Rx Antenna where appropriate") + parser.add_option("-s", "--samp-rate", type="eng_float", default=1e6, + help="set sample rate (bandwidth) [default=%default]") + parser.add_option("-f", "--freq", type="eng_float", default=None, + help="set frequency to FREQ", metavar="FREQ") + parser.add_option("-g", "--gain", type="eng_float", default=None, + help="set gain in dB (default is midpoint)") + parser.add_option("-W", "--waterfall", action="store_true", default=False, + help="Enable waterfall display") + parser.add_option("-S", "--oscilloscope", action="store_true", default=False, + help="Enable oscilloscope 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, + help="Enable fftsink averaging, default=[%default]") + 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]") + + (options, args) = parser.parse_args() + if len(args) != 0: + parser.print_help() + sys.exit(1) + self.options = options + self.show_debug_info = True + + self.src = osmosdr.source_c(options.args) + + # Set the antenna + if(options.antenna): + self.src.set_antenna(options.antenna, 0) + + self.src.set_sample_rate(options.samp_rate) + input_rate = self.src.get_sample_rate() + + if options.waterfall: + self.scope = \ + waterfallsink2.waterfall_sink_c (panel, fft_size=1024, + sample_rate=input_rate) + self.frame.SetMinSize((800, 420)) + elif options.oscilloscope: + self.scope = scopesink2.scope_sink_c(panel, sample_rate=input_rate) + self.frame.SetMinSize((800, 600)) + else: + 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, + avg_alpha=options.avg_alpha, + fft_rate=options.fft_rate) + def fftsink_callback(x, y): + self.set_freq(x) + + self.scope.set_callback(fftsink_callback) + self.frame.SetMinSize((800, 420)) + + self.connect(self.src, self.scope) + + self._build_gui(vbox) + self._setup_events() + + + # set initial values + + if options.gain is None: + # if no gain was specified, use the mid-point in dB + g = self.src.get_gain_range() + options.gain = float(g.start()+g.stop())/2 + + if options.freq is None: + # if no freq was specified, use the mid-point + r = self.src.get_freq_range() + options.freq = float(r.start()+r.stop())/2 + + self.set_gain(options.gain) + + if self.show_debug_info: + self.myform['samprate'].set_value(self.src.get_sample_rate()) + + if not(self.set_freq(options.freq)): + self._set_status_msg("Failed to set initial frequency") + + def _set_status_msg(self, msg): + self.frame.GetStatusBar().SetStatusText(msg, 0) + + def _build_gui(self, vbox): + + def _form_set_freq(kv): + return self.set_freq(kv['freq']) + + vbox.Add(self.scope.win, 10, wx.EXPAND) + + # add control area at the bottom + self.myform = myform = form.form() + hbox = wx.BoxSizer(wx.HORIZONTAL) + hbox.Add((4,0), 0, 0) + myform['freq'] = form.float_field( + parent=self.panel, sizer=hbox, label="Center freq", weight=1, + callback=myform.check_input_and_call(_form_set_freq, + self._set_status_msg)) + + hbox.Add((4,0), 0, 0) + g = self.src.get_gain_range() + + # some configurations don't have gain control + if g.stop() <= g.start(): + glow = 0.0 + ghigh = 1.0 + + else: + glow = g.start() + ghigh = g.stop() + + myform['gain'] = form.slider_field(parent=self.panel, + sizer=hbox, label="Gain", + weight=3, + min=int(glow), max=int(ghigh), + callback=self.set_gain) + hbox.Add((4,0), 0, 0) + vbox.Add(hbox, 0, wx.EXPAND) + + self._build_subpanel(vbox) + + def _build_subpanel(self, vbox_arg): + # build a secondary information panel (sometimes hidden) + + # FIXME figure out how to have this be a subpanel that is always + # created, but has its visibility controlled by foo.Show(True/False) + + def _form_set_sample_rate(kv): + return self.set_sample_rate(kv['samprate']) + + if not(self.show_debug_info): + return + + panel = self.panel + vbox = vbox_arg + myform = self.myform + + hbox = wx.BoxSizer(wx.HORIZONTAL) + + hbox.Add((4,0), 0) + myform['samprate'] = form.float_field( + parent=panel, sizer=hbox, label="Sample Rate", + callback=myform.check_input_and_call(_form_set_sample_rate, + self._set_status_msg)) + vbox.AddSpacer(5) + + vbox.Add(hbox, 0, wx.EXPAND) + vbox.AddSpacer(5) + + def set_freq(self, target_freq): + """ + Set the center frequency we're interested in. + + @param target_freq: frequency in Hz + @rypte: bool + """ + actual_freq = self.src.set_center_freq(target_freq, 0) + self.myform['freq'].set_value(actual_freq) + + if not self.options.oscilloscope: + self.scope.set_baseband_freq(actual_freq) + + return True + + def set_gain(self, gain): + if self.myform.has_key('gain'): + self.myform['gain'].set_value(gain) # update displayed value + self.src.set_gain(gain, 0) + + def set_sample_rate(self, samp_rate): + ok = self.src.set_sample_rate(samp_rate) + input_rate = self.src.get_sample_rate() + self.scope.set_sample_rate(input_rate) + if self.show_debug_info: # update displayed values + self.myform['samprate'].set_value(self.src.get_sample_rate()) + + # set_sample_rate never fails; always falls back to closest requested. + return True + + def _setup_events(self): + if not self.options.waterfall and not self.options.oscilloscope: + self.scope.win.Bind(wx.EVT_LEFT_DCLICK, self.evt_left_dclick) + + def evt_left_dclick(self, event): + (ux, uy) = self.scope.win.GetXY(event) + if event.CmdDown(): + # Re-center on maximum power + points = self.scope.win._points + if self.scope.win.peak_hold: + if self.scope.win.peak_vals is not None: + ind = numpy.argmax(self.scope.win.peak_vals) + else: + ind = int(points.shape()[0]/2) + else: + ind = numpy.argmax(points[:,1]) + + (freq, pwr) = points[ind] + target_freq = freq/self.scope.win._scale_factor + print ind, freq, pwr + self.set_freq(target_freq) + else: + # Re-center on clicked frequency + target_freq = ux/self.scope.win._scale_factor + self.set_freq(target_freq) + +def main (): + app = stdgui2.stdapp(app_top_block, "OSMOCOM Spectrum Browser", nstatus=1) + app.MainLoop() + +if __name__ == '__main__': + main () diff --git a/apps/osmocom_siggen b/apps/osmocom_siggen new file mode 100755 index 0000000..0283fcf --- /dev/null +++ b/apps/osmocom_siggen @@ -0,0 +1,51 @@ +#!/usr/bin/env python +# +# Copyright 2008,2009,2011,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. +# + +from gnuradio import gr +from osmosdr import osmocom_siggen_base as osmocom_siggen +import sys + +def main(): + if gr.enable_realtime_scheduling() != gr.RT_OK: + print "Note: failed to enable realtime scheduling, continuing" + + # Grab command line options and create top block + try: + (options, args) = osmocom_siggen.get_options() + tb = osmocom_siggen.top_block(options, args) + + except RuntimeError, e: + print e + sys.exit(1) + + tb.start() + raw_input('Press Enter to quit: ') + tb.stop() + tb.wait() + +# Make sure to create the top block (tb) within a function: +# That code in main will allow tb to go out of scope on return, +# which will call the decontructor on usrp and stop transmit. +# Whats odd is that grc works fine with tb in the __main__, +# perhaps its because the try/except clauses around tb. +if __name__ == "__main__": + main() diff --git a/apps/osmocom_siggen_base.py b/apps/osmocom_siggen_base.py new file mode 100644 index 0000000..3b5d1d7 --- /dev/null +++ b/apps/osmocom_siggen_base.py @@ -0,0 +1,397 @@ +#!/usr/bin/env python +# +# Copyright 2008,2009,2011,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. +# + +SAMP_RATE_KEY = 'samp_rate' +LINK_RATE_KEY = 'link_rate' +GAIN_KEY = 'gain' +IF_GAIN_KEY = 'if_gain' +BWIDTH_KEY = 'bwidth' +TX_FREQ_KEY = 'tx_freq' +FREQ_CORR_KEY = 'freq_corr' +AMPLITUDE_KEY = 'amplitude' +AMPL_RANGE_KEY = 'ampl_range' +WAVEFORM_FREQ_KEY = 'waveform_freq' +WAVEFORM_OFFSET_KEY = 'waveform_offset' +WAVEFORM2_FREQ_KEY = 'waveform2_freq' +FREQ_RANGE_KEY = 'freq_range' +GAIN_RANGE_KEY = 'gain_range' +IF_GAIN_RANGE_KEY = 'if_gain_range' +BWIDTH_RANGE_KEY = 'bwidth_range' +TYPE_KEY = 'type' + +def setter(ps, key, val): ps[key] = val + +import osmosdr +from gnuradio import gr, gru, eng_notation +from gnuradio.gr.pubsub import pubsub +from gnuradio.eng_option import eng_option +from optparse import OptionParser +import sys +import math + +n2s = eng_notation.num_to_str + +waveforms = { gr.GR_SIN_WAVE : "Complex Sinusoid", + gr.GR_CONST_WAVE : "Constant", + gr.GR_GAUSSIAN : "Gaussian Noise", + gr.GR_UNIFORM : "Uniform Noise", + "2tone" : "Two Tone", + "sweep" : "Sweep" } + +# +# GUI-unaware GNU Radio flowgraph. This may be used either with command +# line applications or GUI applications. +# +class top_block(gr.top_block, pubsub): + def __init__(self, options, args): + gr.top_block.__init__(self) + pubsub.__init__(self) + self._verbose = options.verbose + + #initialize values from options + self._setup_osmosdr(options) + self[SAMP_RATE_KEY] = options.samp_rate + self[TX_FREQ_KEY] = options.tx_freq + self[FREQ_CORR_KEY] = options.freq_corr + self[AMPLITUDE_KEY] = options.amplitude + self[WAVEFORM_FREQ_KEY] = options.waveform_freq + self[WAVEFORM_OFFSET_KEY] = options.offset + self[WAVEFORM2_FREQ_KEY] = options.waveform2_freq + + #subscribe set methods + self.subscribe(SAMP_RATE_KEY, self.set_samp_rate) + self.subscribe(GAIN_KEY, self.set_gain) + self.subscribe(IF_GAIN_KEY, self.set_if_gain) + self.subscribe(BWIDTH_KEY, self.set_bandwidth) + self.subscribe(TX_FREQ_KEY, self.set_freq) + self.subscribe(FREQ_CORR_KEY, self.set_freq_corr) + self.subscribe(AMPLITUDE_KEY, self.set_amplitude) + self.subscribe(WAVEFORM_FREQ_KEY, self.set_waveform_freq) + self.subscribe(WAVEFORM2_FREQ_KEY, self.set_waveform2_freq) + self.subscribe(TYPE_KEY, self.set_waveform) + + #force update on pubsub keys + for key in (SAMP_RATE_KEY, GAIN_KEY, IF_GAIN_KEY, BWIDTH_KEY, + TX_FREQ_KEY, FREQ_CORR_KEY, AMPLITUDE_KEY, + WAVEFORM_FREQ_KEY, WAVEFORM_OFFSET_KEY, WAVEFORM2_FREQ_KEY): +# print "key: ", key, "=", self[key] + self[key] = self[key] + self[TYPE_KEY] = options.type #set type last + + def _setup_osmosdr(self, options): + self._sink = osmosdr.sink_c(options.args) + self._sink.set_sample_rate(options.samp_rate) + + # Set the gain from options + if(options.gain): + self._sink.set_gain(options.gain) + + # Set the antenna + if(options.antenna): + self._sink.set_antenna(options.antenna, 0) + + self.publish(FREQ_RANGE_KEY, self._sink.get_freq_range) + self.publish(GAIN_RANGE_KEY, self._get_rf_gain_range) + self.publish(IF_GAIN_RANGE_KEY, self._get_if_gain_range) + self.publish(BWIDTH_RANGE_KEY, self._sink.get_bandwidth_range) + self.publish(GAIN_KEY, self._get_rf_gain) + self.publish(IF_GAIN_KEY, self._get_if_gain) + self.publish(BWIDTH_KEY, self._sink.get_bandwidth) + + def _get_rf_gain_range(self): + return self._sink.get_gain_range("RF") + + def _get_if_gain_range(self): + return self._sink.get_gain_range("IF") + + def _get_rf_gain(self): + return self._sink.get_gain("RF") + + def _get_if_gain(self): + return self._sink.get_gain("IF") + + def _set_tx_amplitude(self, ampl): + """ + Sets the transmit amplitude + @param ampl the amplitude or None for automatic + """ + ampl_range = self[AMPL_RANGE_KEY] + if ampl is None: + ampl = (ampl_range[1] - ampl_range[0])*0.3 + ampl_range[0] + self[AMPLITUDE_KEY] = max(ampl_range[0], min(ampl, ampl_range[1])) + + def set_samp_rate(self, sr): + self._sink.set_sample_rate(sr) + sr = self._sink.get_sample_rate() + + if self[TYPE_KEY] in (gr.GR_SIN_WAVE, gr.GR_CONST_WAVE): + self._src.set_sampling_freq(self[SAMP_RATE_KEY]) + elif self[TYPE_KEY] == "2tone": + self._src1.set_sampling_freq(self[SAMP_RATE_KEY]) + self._src2.set_sampling_freq(self[SAMP_RATE_KEY]) + elif self[TYPE_KEY] == "sweep": + self._src1.set_sampling_freq(self[SAMP_RATE_KEY]) + self._src2.set_sampling_freq(self[WAVEFORM_FREQ_KEY]*2*math.pi/self[SAMP_RATE_KEY]) + else: + return True # Waveform not yet set + + if self._verbose: + print "Set sample rate to:", sr + + return True + + def set_gain(self, gain): + if gain is None: + g = self[GAIN_RANGE_KEY] + gain = float(g.start()+g.stop())/2 + if self._verbose: + print "Using auto-calculated mid-point RF gain" + self[GAIN_KEY] = gain + return + gain = self._sink.set_gain(gain, "RF") + if self._verbose: + print "Set RF gain to:", gain + + def set_if_gain(self, gain): + if gain is None: + g = self[IF_GAIN_RANGE_KEY] + gain = float(g.start()+g.stop())/2 + if self._verbose: + print "Using auto-calculated mid-point IF gain" + self[IF_GAIN_KEY] = gain + return + gain = self._sink.set_gain(gain, "IF") + if self._verbose: + print "Set IF gain to:", gain + + def set_bandwidth(self, bw): + bw = self._sink.set_bandwidth(bw) + if self._verbose: + print "Set bandwidth to:", bw + + def set_freq(self, target_freq): + + if target_freq is None: + f = self[FREQ_RANGE_KEY] + target_freq = float(f.start()+f.stop())/2.0 + if self._verbose: + print "Using auto-calculated mid-point frequency" + self[TX_FREQ_KEY] = target_freq + return + + tr = self._sink.set_center_freq(target_freq) + if tr is not None: + self._freq = tr + if self._verbose: + print "Set center frequency to", tr + elif self._verbose: + print "Failed to set freq." + return tr + + def set_freq_corr(self, ppm): + if ppm is None: + if self._verbose: + print "Setting freq corrrection to 0" + self[FREQ_CORR_KEY] = 0 + return + + ppm = self._sink.set_freq_corr(ppm) + if self._verbose: + print "Set freq correction to:", ppm + + def set_waveform_freq(self, freq): + if self[TYPE_KEY] == gr.GR_SIN_WAVE: + self._src.set_frequency(freq) + elif self[TYPE_KEY] == "2tone": + self._src1.set_frequency(freq) + elif self[TYPE_KEY] == 'sweep': + #there is no set sensitivity, redo fg + self[TYPE_KEY] = self[TYPE_KEY] + return True + + def set_waveform2_freq(self, freq): + if freq is None: + self[WAVEFORM2_FREQ_KEY] = -self[WAVEFORM_FREQ_KEY] + return + if self[TYPE_KEY] == "2tone": + self._src2.set_frequency(freq) + elif self[TYPE_KEY] == "sweep": + self._src1.set_frequency(freq) + return True + + def set_waveform(self, type): + self.lock() + self.disconnect_all() + if type == gr.GR_SIN_WAVE or type == gr.GR_CONST_WAVE: + self._src = gr.sig_source_c(self[SAMP_RATE_KEY], # Sample rate + type, # Waveform type + self[WAVEFORM_FREQ_KEY], # Waveform frequency + self[AMPLITUDE_KEY], # Waveform amplitude + self[WAVEFORM_OFFSET_KEY]) # Waveform offset + elif type == gr.GR_GAUSSIAN or type == gr.GR_UNIFORM: + self._src = gr.noise_source_c(type, self[AMPLITUDE_KEY]) + elif type == "2tone": + self._src1 = gr.sig_source_c(self[SAMP_RATE_KEY], + gr.GR_SIN_WAVE, + self[WAVEFORM_FREQ_KEY], + self[AMPLITUDE_KEY]/2.0, + 0) + if(self[WAVEFORM2_FREQ_KEY] is None): + self[WAVEFORM2_FREQ_KEY] = -self[WAVEFORM_FREQ_KEY] + + self._src2 = gr.sig_source_c(self[SAMP_RATE_KEY], + gr.GR_SIN_WAVE, + self[WAVEFORM2_FREQ_KEY], + self[AMPLITUDE_KEY]/2.0, + 0) + self._src = gr.add_cc() + self.connect(self._src1,(self._src,0)) + self.connect(self._src2,(self._src,1)) + elif type == "sweep": + # rf freq is center frequency + # waveform_freq is total swept width + # waveform2_freq is sweep rate + # will sweep from (rf_freq-waveform_freq/2) to (rf_freq+waveform_freq/2) + if self[WAVEFORM2_FREQ_KEY] is None: + self[WAVEFORM2_FREQ_KEY] = 0.1 + + self._src1 = gr.sig_source_f(self[SAMP_RATE_KEY], + gr.GR_TRI_WAVE, + self[WAVEFORM2_FREQ_KEY], + 1.0, + -0.5) + self._src2 = gr.frequency_modulator_fc(self[WAVEFORM_FREQ_KEY]*2*math.pi/self[SAMP_RATE_KEY]) + self._src = gr.multiply_const_cc(self[AMPLITUDE_KEY]) + self.connect(self._src1,self._src2,self._src) + else: + raise RuntimeError("Unknown waveform type") + + self.connect(self._src, self._sink) + self.unlock() + + if self._verbose: + print "Set baseband modulation to:", waveforms[type] + if type == gr.GR_SIN_WAVE: + print "Modulation frequency: %sHz" % (n2s(self[WAVEFORM_FREQ_KEY]),) + print "Initial phase:", self[WAVEFORM_OFFSET_KEY] + elif type == "2tone": + print "Tone 1: %sHz" % (n2s(self[WAVEFORM_FREQ_KEY]),) + print "Tone 2: %sHz" % (n2s(self[WAVEFORM2_FREQ_KEY]),) + elif type == "sweep": + print "Sweeping across %sHz to %sHz" % (n2s(-self[WAVEFORM_FREQ_KEY]/2.0),n2s(self[WAVEFORM_FREQ_KEY]/2.0)) + print "Sweep rate: %sHz" % (n2s(self[WAVEFORM2_FREQ_KEY]),) + print "TX amplitude:", self[AMPLITUDE_KEY] + + + def set_amplitude(self, amplitude): + if amplitude < 0.0 or amplitude > 1.0: + if self._verbose: + print "Amplitude out of range:", amplitude + return False + + if self[TYPE_KEY] in (gr.GR_SIN_WAVE, gr.GR_CONST_WAVE, gr.GR_GAUSSIAN, gr.GR_UNIFORM): + self._src.set_amplitude(amplitude) + elif self[TYPE_KEY] == "2tone": + self._src1.set_amplitude(amplitude/2.0) + self._src2.set_amplitude(amplitude/2.0) + elif self[TYPE_KEY] == "sweep": + self._src.set_k(amplitude) + else: + return True # Waveform not yet set + + if self._verbose: + print "Set amplitude to:", amplitude + return True + +def get_options(): + usage="%prog: [options]" + + parser = OptionParser(option_class=eng_option, usage=usage) + parser.add_option("-a", "--args", type="string", default="", + help="Device args, [default=%default]") + parser.add_option("-A", "--antenna", type="string", default=None, + help="Select Rx Antenna where appropriate") + parser.add_option("-s", "--samp-rate", type="eng_float", default=1e6, + help="Set sample rate (bandwidth) [default=%default]") + parser.add_option("-g", "--gain", type="eng_float", default=None, + help="Set gain in dB (default is midpoint)") + parser.add_option("-f", "--tx-freq", type="eng_float", default=None, + help="Set carrier frequency to FREQ [default=mid-point]", + metavar="FREQ") + parser.add_option("-c", "--freq-corr", type="int", default=None, + help="Set carrier frequency correction [default=0]") + parser.add_option("-x", "--waveform-freq", type="eng_float", default=0, + help="Set baseband waveform frequency to FREQ [default=%default]") + parser.add_option("-y", "--waveform2-freq", type="eng_float", default=None, + help="Set 2nd waveform frequency to FREQ [default=%default]") + parser.add_option("--sine", dest="type", action="store_const", const=gr.GR_SIN_WAVE, + help="Generate a carrier modulated by a complex sine wave", + default=gr.GR_SIN_WAVE) + parser.add_option("--const", dest="type", action="store_const", const=gr.GR_CONST_WAVE, + help="Generate a constant carrier") + parser.add_option("--offset", type="eng_float", default=0, + help="Set waveform phase offset to OFFSET [default=%default]") + parser.add_option("--gaussian", dest="type", action="store_const", const=gr.GR_GAUSSIAN, + help="Generate Gaussian random output") + parser.add_option("--uniform", dest="type", action="store_const", const=gr.GR_UNIFORM, + help="Generate Uniform random output") + parser.add_option("--2tone", dest="type", action="store_const", const="2tone", + help="Generate Two Tone signal for IMD testing") + parser.add_option("--sweep", dest="type", action="store_const", const="sweep", + help="Generate a swept sine wave") + parser.add_option("", "--amplitude", type="eng_float", default=0.3, + help="Set output amplitude to AMPL (0.0-1.0) [default=%default]", + metavar="AMPL") + parser.add_option("-v", "--verbose", action="store_true", default=False, + help="Use verbose console output [default=%default]") + + (options, args) = parser.parse_args() + + return (options, args) + +# If this script is executed, the following runs. If it is imported, +# the below does not run. +def test_main(): + if gr.enable_realtime_scheduling() != gr.RT_OK: + print "Note: failed to enable realtime scheduling, continuing" + + # Grab command line options and create top block + try: + (options, args) = get_options() + tb = top_block(options, args) + + except RuntimeError, e: + print e + sys.exit(1) + + tb.start() + raw_input('Press Enter to quit: ') + tb.stop() + tb.wait() + +# Make sure to create the top block (tb) within a function: +# That code in main will allow tb to go out of scope on return, +# which will call the decontructor on radio and stop transmit. +# Whats odd is that grc works fine with tb in the __main__, +# perhaps its because the try/except clauses around tb. +if __name__ == "__main__": + test_main() diff --git a/apps/osmocom_siggen_gui b/apps/osmocom_siggen_gui new file mode 100755 index 0000000..86003c4 --- /dev/null +++ b/apps/osmocom_siggen_gui @@ -0,0 +1,374 @@ +#!/usr/bin/env python +# +# Copyright 2009,2011,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. +# + +from gnuradio import gr +from gnuradio.gr.pubsub import pubsub +from osmosdr import osmocom_siggen_base as osmocom_siggen +import sys, math + +try: + from gnuradio.wxgui import gui, forms + import wx +except ImportError: + sys.stderr.write("Error importing GNU Radio's wxgui. Please make sure gr-wxgui is installed.\n") + sys.exit(1) + +class app_gui(pubsub): + def __init__(self, frame, panel, vbox, top_block, options, args): + pubsub.__init__(self) + self.frame = frame # Use for top-level application window frame + self.panel = panel # Use as parent class for created windows + self.vbox = vbox # Use as sizer for created windows + self.tb = top_block # GUI-unaware flowgraph class + self.options = options # Supplied command-line options + self.args = args # Supplied command-line arguments + self.build_gui() + + # Event response handlers + def evt_set_status_msg(self, msg): + self.frame.SetStatusText(msg, 0) + + # GUI construction + def build_gui(self): + self.vbox.AddSpacer(3) + self.vbox.AddStretchSpacer() + ################################################## + # Baseband controls + ################################################## + bb_vbox = forms.static_box_sizer(parent=self.panel, label="Baseband Modulation", orient=wx.VERTICAL, bold=True) + self.vbox.Add(bb_vbox, 0, wx.EXPAND) + sine_bb_hbox = wx.BoxSizer(wx.HORIZONTAL) + sweep_bb_hbox = wx.BoxSizer(wx.HORIZONTAL) + tone_bb_hbox = wx.BoxSizer(wx.HORIZONTAL) + self.vbox.AddSpacer(5) + self.vbox.AddStretchSpacer() + #callback to show/hide forms + def set_type(type): + sine_bb_hbox.ShowItems(type == gr.GR_SIN_WAVE) + sweep_bb_hbox.ShowItems(type == 'sweep') + tone_bb_hbox.ShowItems(type == '2tone') + self.vbox.Layout() + self.tb.subscribe(osmocom_siggen.TYPE_KEY, set_type) + #create sine forms + sine_bb_hbox.AddSpacer(5) + forms.text_box( + parent=self.panel, sizer=sine_bb_hbox, + label='Frequency (Hz)', + ps=self.tb, + key=osmocom_siggen.WAVEFORM_FREQ_KEY, + converter=forms.float_converter(), + ) + sine_bb_hbox.AddStretchSpacer() + #create sweep forms + sweep_bb_hbox.AddSpacer(5) + forms.text_box( + parent=self.panel, sizer=sweep_bb_hbox, + label='Sweep Width (Hz)', + ps=self.tb, + key=osmocom_siggen.WAVEFORM_FREQ_KEY, + converter=forms.float_converter(), + ) + sweep_bb_hbox.AddStretchSpacer() + forms.text_box( + parent=self.panel, sizer=sweep_bb_hbox, + label='Sweep Rate (Hz)', + ps=self.tb, + key=osmocom_siggen.WAVEFORM2_FREQ_KEY, + converter=forms.float_converter(), + ) + sweep_bb_hbox.AddStretchSpacer() + #create 2tone forms + tone_bb_hbox.AddSpacer(5) + forms.text_box( + parent=self.panel, sizer=tone_bb_hbox, + label='Tone 1 (Hz)', + ps=self.tb, + key=osmocom_siggen.WAVEFORM_FREQ_KEY, + converter=forms.float_converter(), + ) + tone_bb_hbox.AddStretchSpacer() + forms.text_box( + parent=self.panel, sizer=tone_bb_hbox, + label='Tone 2 (Hz)', + ps=self.tb, + key=osmocom_siggen.WAVEFORM2_FREQ_KEY, + converter=forms.float_converter(), + ) + tone_bb_hbox.AddStretchSpacer() + forms.radio_buttons( + parent=self.panel, sizer=bb_vbox, + choices=osmocom_siggen.waveforms.keys(), + labels=osmocom_siggen.waveforms.values(), + ps=self.tb, + key=osmocom_siggen.TYPE_KEY, + style=wx.NO_BORDER | wx.RA_HORIZONTAL, + ) + bb_vbox.AddSpacer(5) + bb_vbox.Add(sine_bb_hbox, 0, wx.EXPAND) + bb_vbox.Add(sweep_bb_hbox, 0, wx.EXPAND) + bb_vbox.Add(tone_bb_hbox, 0, wx.EXPAND) + set_type(self.tb[osmocom_siggen.TYPE_KEY]) + + ################################################## + # Frequency controls + ################################################## + 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 + self.vbox.Add(fc_vbox, 0, wx.EXPAND) + self.vbox.AddSpacer(5) + self.vbox.AddStretchSpacer() + + 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.tb, + key=osmocom_siggen.TX_FREQ_KEY, + ) + freq_hbox.AddSpacer(5) + + forms.slider( + parent=self.panel, sizer=freq_hbox, + proportion=2, + ps=self.tb, + key=osmocom_siggen.TX_FREQ_KEY, + minimum=self.tb[osmocom_siggen.FREQ_RANGE_KEY].start(), + maximum=self.tb[osmocom_siggen.FREQ_RANGE_KEY].stop(), + num_steps=100, + ) + freq_hbox.AddSpacer(3) + + corr_hbox.AddSpacer(3) + forms.text_box( + parent=self.panel, sizer=corr_hbox, + label='Frequency Correction (ppm)', + proportion=1, + converter=forms.int_converter(), + ps=self.tb, + key=osmocom_siggen.FREQ_CORR_KEY, + ) + corr_hbox.AddSpacer(5) + + forms.slider( + parent=self.panel, sizer=corr_hbox, + proportion=2, + ps=self.tb, + key=osmocom_siggen.FREQ_CORR_KEY, + minimum=-100, + maximum=+100, + num_steps=201, + ) + corr_hbox.AddSpacer(3) + + ################################################## + # Amplitude controls + ################################################## + amp_vbox = forms.static_box_sizer(parent=self.panel, + label="Amplitude", + orient=wx.VERTICAL, + bold=True) + amp_vbox.AddSpacer(3) + # First row of amp controls (ampl) + lvl_hbox = wx.BoxSizer(wx.HORIZONTAL) + amp_vbox.Add(lvl_hbox, 0, wx.EXPAND) + amp_vbox.AddSpacer(5) + # Second row of amp controls (tx gain) + gain_hbox = wx.BoxSizer(wx.HORIZONTAL) + amp_vbox.Add(gain_hbox, 0, wx.EXPAND) + amp_vbox.AddSpacer(3) + if_gain_hbox = wx.BoxSizer(wx.HORIZONTAL) + amp_vbox.Add(if_gain_hbox, 0, wx.EXPAND) + amp_vbox.AddSpacer(3) + + self.vbox.Add(amp_vbox, 0, wx.EXPAND) + self.vbox.AddSpacer(5) + self.vbox.AddStretchSpacer() + + lvl_hbox.AddSpacer(3) + forms.text_box( + parent=self.panel, sizer=lvl_hbox, + proportion=1, + converter=forms.float_converter(), + ps=self.tb, + key=osmocom_siggen.AMPLITUDE_KEY, + label="Level (0.0-1.0)", + ) + lvl_hbox.AddSpacer(5) + forms.log_slider( + parent=self.panel, sizer=lvl_hbox, + proportion=2, + ps=self.tb, + key=osmocom_siggen.AMPLITUDE_KEY, + min_exp=-6, + max_exp=0, + base=10, + num_steps=100, + ) + lvl_hbox.AddSpacer(3) + + if self.tb[osmocom_siggen.GAIN_RANGE_KEY].start() < self.tb[osmocom_siggen.GAIN_RANGE_KEY].stop(): + gain_hbox.AddSpacer(3) + forms.text_box( + parent=self.panel, sizer=gain_hbox, + proportion=1, + converter=forms.float_converter(), + ps=self.tb, + key=osmocom_siggen.GAIN_KEY, + label="RF Gain (dB)", + ) + gain_hbox.AddSpacer(5) + forms.slider( + parent=self.panel, sizer=gain_hbox, + proportion=2, + ps=self.tb, + key=osmocom_siggen.GAIN_KEY, + minimum=self.tb[osmocom_siggen.GAIN_RANGE_KEY].start(), + maximum=self.tb[osmocom_siggen.GAIN_RANGE_KEY].stop(), + step_size=self.tb[osmocom_siggen.GAIN_RANGE_KEY].step(), + ) + gain_hbox.AddSpacer(3) + + if self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].start() < self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].stop(): + if_gain_hbox.AddSpacer(3) + forms.text_box( + parent=self.panel, sizer=if_gain_hbox, + proportion=1, + converter=forms.float_converter(), + ps=self.tb, + key=osmocom_siggen.IF_GAIN_KEY, + label="IF Gain (dB)", + ) + if_gain_hbox.AddSpacer(5) + forms.slider( + parent=self.panel, sizer=if_gain_hbox, + proportion=2, + ps=self.tb, + key=osmocom_siggen.IF_GAIN_KEY, + minimum=self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].start(), + maximum=self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].stop(), + step_size=self.tb[osmocom_siggen.IF_GAIN_RANGE_KEY].step(), + ) + if_gain_hbox.AddSpacer(3) + + ################################################## + # Bandiwdth controls + ################################################## + 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) + + self.vbox.Add(bwidth_vbox, 0, wx.EXPAND) + self.vbox.AddSpacer(5) + self.vbox.AddStretchSpacer() + + if self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].start() < self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].stop(): + bwidth_hbox.AddSpacer(3) + forms.text_box( + parent=self.panel, sizer=bwidth_hbox, + proportion=1, + converter=forms.float_converter(), + ps=self.tb, + key=osmocom_siggen.BWIDTH_KEY, + label="Bandwidth (Hz)", + ) + bwidth_hbox.AddSpacer(5) + forms.slider( + parent=self.panel, sizer=bwidth_hbox, + proportion=2, + ps=self.tb, + key=osmocom_siggen.BWIDTH_KEY, + minimum=self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].start(), + maximum=self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].stop(), + step_size=self.tb[osmocom_siggen.BWIDTH_RANGE_KEY].step(), + ) + bwidth_hbox.AddSpacer(3) + + ################################################## + # Sample Rate controls + ################################################## + sam_hbox = forms.static_box_sizer(parent=self.panel, + label="Sample Rate", + orient=wx.HORIZONTAL, + bold=True) + self.vbox.Add(sam_hbox, 0, wx.EXPAND) + self.vbox.AddSpacer(5) + self.vbox.AddStretchSpacer() + sam_hbox.AddStretchSpacer(20) + forms.static_text( + parent=self.panel, sizer=sam_hbox, + label='Sample Rate (sps)', + ps=self.tb, + key=osmocom_siggen.SAMP_RATE_KEY, + converter=forms.float_converter(), + ) + sam_hbox.AddStretchSpacer(20) + +def main(): + try: + # Get command line parameters + (options, args) = osmocom_siggen.get_options() + + # Create the top block using these + tb = osmocom_siggen.top_block(options, args) + + # Create the GUI application + app = gui.app(top_block=tb, # Constructed top block + gui=app_gui, # User interface class + options=options, # Command line options + args=args, # Command line args + title="OSMOCOM Signal Generator", # Top window title + nstatus=1, # Number of status lines + start=True, # Whether to start flowgraph + realtime=True) # Whether to set realtime priority + + # And run it + app.MainLoop() + + except RuntimeError, e: + print e + sys.exit(1) + +# Make sure to create the top block (tb) within a function: That code +# in main will allow tb to go out of scope on return, which will call +# the decontructor on radio device and stop transmit. Whats odd is that +# grc works fine with tb in the __main__, perhaps its because the +# try/except clauses around tb. +if __name__ == "__main__": main() |