3 # Copyright 2008 Free Software Foundation, Inc.
5 # This file is part of GNU Radio
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)
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.
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.
23 from gnuradio import gr, gru, eng_notation, optfir, window
24 from gnuradio import msdd
25 from gnuradio.eng_option import eng_option
26 from optparse import OptionParser
31 from numpy import array
34 matplotlib.interactive(True)
35 matplotlib.use('TkAgg')
37 class tune(gr.feval_dd):
39 This class allows C++ code to callback into python.
41 def __init__(self, tb):
42 gr.feval_dd.__init__(self)
45 def eval(self, ignore):
47 This method is called from gr.bin_statistics_f when it wants to change
48 the center frequency. This method tunes the front end to the new center
49 frequency, and returns the new frequency as its result.
52 # We use this try block so that if something goes wrong from here
53 # down, at least we'll have a prayer of knowing what went wrong.
54 # Without this, you get a very mysterious:
56 # terminate called after throwing an instance of 'Swig::DirectorMethodException'
59 # message on stderr. Not exactly helpful ;)
61 new_freq = self.tb.set_next_freq()
65 print "tune: Exception: ", e
68 class parse_msg(object):
69 def __init__(self, sample_rate, percent, alpha=0.01):
70 self.axis_font_size = 16
71 self.label_font_size = 18
72 self.title_font_size = 20
75 self.fig = figure(1, facecolor="w", figsize=(12,9))
76 self.sp = self.fig.add_subplot(1,1,1)
77 self.pl = self.sp.plot(range(100), 100*[1,])
79 params = {'backend': 'ps',
80 'xtick.labelsize': self.axis_font_size,
81 'ytick.labelsize': self.axis_font_size,
83 rcParams.update(params)
85 self.sp.set_title(("FFT"), fontsize=self.title_font_size, fontweight="bold")
86 self.sp.set_xlabel("Frequency (Hz)", fontsize=self.label_font_size, fontweight="bold")
87 self.sp.set_ylabel("Magnitude (dB)", fontsize=self.label_font_size, fontweight="bold")
88 self.text_alpha = figtext(0.10, 0.94, ('Moving average alpha: %s' % alpha), weight="heavy", size=self.text_size)
91 self.freqrange = list()
92 self.data = list() #array('f')
100 self.sample_rate = sample_rate
101 self.percent = (1.0-percent)/2.0
103 def parse(self, msg):
104 self.center_freq = msg.arg1()
105 self.vlen = int(msg.arg2())
106 assert(msg.length() == self.vlen * gr.sizeof_float)
109 if(self.center_freq < self.last_cfreq):
110 print "Plotting spectrum\n"
113 self.pl[0].set_data([self.freqrange, self.data])
114 self.sp.set_ylim([min(self.data), max(self.data)])
115 self.sp.set_xlim([min(self.freqrange), max(self.freqrange)])
120 self.freqrange = list()
123 self.last_cfreq = self.center_freq
125 startind = int(self.percent * self.vlen)
126 endind = int((1.0 - self.percent) * self.vlen)
128 fstep = self.sample_rate / self.vlen
129 f = [self.center_freq - self.sample_rate/2.0 + i*fstep for i in range(startind, endind)]
133 d = struct.unpack('%df' % (self.vlen,), t)
136 for i in range(startind, endind):
137 self.data[self.index] = (1.0-self.alpha)*self.data[self.index] + (self.alpha)*d[i]
140 self.data += [di for di in d[startind:endind]]
143 class my_top_block(gr.top_block):
146 gr.top_block.__init__(self)
148 usage = "usage: %prog [options] host min_freq max_freq"
149 parser = OptionParser(option_class=eng_option, usage=usage)
150 parser.add_option("-g", "--gain", type="eng_float", default=None,
151 help="set gain in dB (default is midpoint)")
152 parser.add_option("", "--tune-delay", type="eng_float", default=5e-5, metavar="SECS",
153 help="time to delay (in seconds) after changing frequency [default=%default]")
154 parser.add_option("", "--dwell-delay", type="eng_float", default=50e-5, metavar="SECS",
155 help="time to dwell (in seconds) at a given frequncy [default=%default]")
156 parser.add_option("-F", "--fft-size", type="int", default=256,
157 help="specify number of FFT bins [default=%default]")
158 parser.add_option("-d", "--decim", type="intx", default=16,
159 help="set decimation to DECIM [default=%default]")
160 parser.add_option("", "--real-time", action="store_true", default=False,
161 help="Attempt to enable real-time scheduling")
163 (options, args) = parser.parse_args()
168 self.address = args[0]
169 self.min_freq = eng_notation.str_to_num(args[1])
170 self.max_freq = eng_notation.str_to_num(args[2])
172 self.decim = options.decim
173 self.gain = options.gain
175 if self.min_freq > self.max_freq:
176 self.min_freq, self.max_freq = self.max_freq, self.min_freq # swap them
178 self.fft_size = options.fft_size
180 if not options.real_time:
183 # Attempt to enable realtime scheduling
184 r = gr.enable_realtime_scheduling()
189 print "Note: failed to enable realtime scheduling"
192 self.int_rate = adc_rate / self.decim
193 print "Sampling rate: ", self.int_rate
197 self.src = msdd.source_simple(self.address, self.port)
198 self.src.set_decim_rate(self.decim)
200 self.set_gain(self.gain)
201 self.set_freq(self.min_freq)
203 s2v = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size)
205 mywindow = window.blackmanharris(self.fft_size)
206 fft = gr.fft_vcc(self.fft_size, True, mywindow, True)
211 norm = gr.multiply_const_cc(1.0/self.fft_size)
212 c2mag = gr.complex_to_mag_squared(self.fft_size)
214 # FIXME the log10 primitive is dog slow
215 log = gr.nlog10_ff(10, self.fft_size,
216 -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size))
218 # Set the freq_step to % of the actual data throughput.
219 # This allows us to discard the bins on both ends of the spectrum.
222 self.freq_step = self.percent * self.int_rate
223 self.min_center_freq = self.min_freq + self.freq_step/2
224 nsteps = math.ceil((self.max_freq - self.min_freq) / self.freq_step)
225 self.max_center_freq = self.min_center_freq + (nsteps * self.freq_step)
227 self.next_freq = self.min_center_freq
229 tune_delay = max(0, int(round(options.tune_delay * self.int_rate / self.fft_size))) # in fft_frames
230 dwell_delay = max(1, int(round(options.dwell_delay * self.int_rate / self.fft_size))) # in fft_frames
232 self.msgq = gr.msg_queue(16)
233 self._tune_callback = tune(self) # hang on to this to keep it from being GC'd
234 stats = gr.bin_statistics_f(self.fft_size, self.msgq,
235 self._tune_callback, tune_delay, dwell_delay)
237 # FIXME leave out the log10 until we speed it up
238 self.connect(self.src, s2v, fft, c2mag, log, stats)
241 def set_next_freq(self):
242 target_freq = self.next_freq
243 self.next_freq = self.next_freq + self.freq_step
244 if self.next_freq >= self.max_center_freq:
245 self.next_freq = self.min_center_freq
247 if not self.set_freq(target_freq):
248 print "Failed to set frequency to", target_freq
253 def set_freq(self, target_freq):
255 Set the center frequency we're interested in.
257 @param target_freq: frequency in Hz
261 return self.src.set_rx_freq(0, target_freq)
264 def set_gain(self, gain):
265 self.src.set_pga(0, gain)
269 msgparser = parse_msg(tb.int_rate, tb.percent)
273 # Get the next message sent from the C++ code (blocking call).
274 # It contains the center frequency and the mag squared of the fft
275 msgparser.parse(tb.msgq.delete_head())
277 # Print center freq so we know that something is happening...
278 print msgparser.center_freq
280 # FIXME do something useful with the data...
282 # m.data are the mag_squared of the fft output (they are in the
283 # standard order. I.e., bin 0 == DC.)
284 # You'll probably want to do the equivalent of "fftshift" on them
285 # m.raw_data is a string that contains the binary floats.
286 # You could write this as binary to a file.
289 if __name__ == '__main__':
292 tb.start() # start executing flow graph in another thread...
295 except KeyboardInterrupt: