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)
This commit is contained in:
Dimitri Stolnikov 2013-04-28 12:41:35 +02:00
parent a5bdb27240
commit a31ea525fb
5 changed files with 1103 additions and 2 deletions

View File

@ -20,6 +20,15 @@
include(GrPython)
GR_PYTHON_INSTALL(
PROGRAMS
DESTINATION bin
FILES
osmocom_siggen_base.py
DESTINATION ${GR_PYTHON_DIR}/osmosdr
)
GR_PYTHON_INSTALL(
PROGRAMS
osmocom_fft
osmocom_siggen
osmocom_siggen_gui
DESTINATION ${GR_RUNTIME_DIR}
)

270
apps/osmocom_fft Executable file
View File

@ -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 ()

51
apps/osmocom_siggen Executable file
View File

@ -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()

397
apps/osmocom_siggen_base.py Normal file
View File

@ -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()

374
apps/osmocom_siggen_gui Executable file
View File

@ -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()