Imported Upstream version 3.2.2
[debian/gnuradio] / gnuradio-examples / python / usrp / usrp_spectrum_sense.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2005,2007 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
23 from gnuradio import gr, gru, eng_notation, optfir, window
24 from gnuradio import audio
25 from gnuradio import usrp
26 from gnuradio.eng_option import eng_option
27 from optparse import OptionParser
28 from usrpm import usrp_dbid
29 import sys
30 import math
31 import struct
32
33
34 class tune(gr.feval_dd):
35     """
36     This class allows C++ code to callback into python.
37     """
38     def __init__(self, tb):
39         gr.feval_dd.__init__(self)
40         self.tb = tb
41
42     def eval(self, ignore):
43         """
44         This method is called from gr.bin_statistics_f when it wants to change
45         the center frequency.  This method tunes the front end to the new center
46         frequency, and returns the new frequency as its result.
47         """
48         try:
49             # We use this try block so that if something goes wrong from here 
50             # down, at least we'll have a prayer of knowing what went wrong.
51             # Without this, you get a very mysterious:
52             #
53             #   terminate called after throwing an instance of 'Swig::DirectorMethodException'
54             #   Aborted
55             #
56             # message on stderr.  Not exactly helpful ;)
57
58             new_freq = self.tb.set_next_freq()
59             return new_freq
60
61         except Exception, e:
62             print "tune: Exception: ", e
63
64
65 class parse_msg(object):
66     def __init__(self, msg):
67         self.center_freq = msg.arg1()
68         self.vlen = int(msg.arg2())
69         assert(msg.length() == self.vlen * gr.sizeof_float)
70
71         # FIXME consider using Numarray or NumPy vector
72         t = msg.to_string()
73         self.raw_data = t
74         self.data = struct.unpack('%df' % (self.vlen,), t)
75
76
77 class my_top_block(gr.top_block):
78
79     def __init__(self):
80         gr.top_block.__init__(self)
81
82         usage = "usage: %prog [options] min_freq max_freq"
83         parser = OptionParser(option_class=eng_option, usage=usage)
84         parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=(0,0),
85                           help="select USRP Rx side A or B (default=A)")
86         parser.add_option("-g", "--gain", type="eng_float", default=None,
87                           help="set gain in dB (default is midpoint)")
88         parser.add_option("", "--tune-delay", type="eng_float", default=1e-3, metavar="SECS",
89                           help="time to delay (in seconds) after changing frequency [default=%default]")
90         parser.add_option("", "--dwell-delay", type="eng_float", default=10e-3, metavar="SECS",
91                           help="time to dwell (in seconds) at a given frequncy [default=%default]")
92         parser.add_option("-F", "--fft-size", type="int", default=256,
93                           help="specify number of FFT bins [default=%default]")
94         parser.add_option("-d", "--decim", type="intx", default=16,
95                           help="set decimation to DECIM [default=%default]")
96         parser.add_option("", "--real-time", action="store_true", default=False,
97                           help="Attempt to enable real-time scheduling")
98         parser.add_option("-B", "--fusb-block-size", type="int", default=0,
99                           help="specify fast usb block size [default=%default]")
100         parser.add_option("-N", "--fusb-nblocks", type="int", default=0,
101                           help="specify number of fast usb blocks [default=%default]")
102
103         (options, args) = parser.parse_args()
104         if len(args) != 2:
105             parser.print_help()
106             sys.exit(1)
107
108         self.min_freq = eng_notation.str_to_num(args[0])
109         self.max_freq = eng_notation.str_to_num(args[1])
110
111         if self.min_freq > self.max_freq:
112             self.min_freq, self.max_freq = self.max_freq, self.min_freq   # swap them
113
114         self.fft_size = options.fft_size
115
116
117         if not options.real_time:
118             realtime = False
119         else:
120             # Attempt to enable realtime scheduling
121             r = gr.enable_realtime_scheduling()
122             if r == gr.RT_OK:
123                 realtime = True
124             else:
125                 realtime = False
126                 print "Note: failed to enable realtime scheduling"
127
128         # If the user hasn't set the fusb_* parameters on the command line,
129         # pick some values that will reduce latency.
130
131         if 1:
132             if options.fusb_block_size == 0 and options.fusb_nblocks == 0:
133                 if realtime:                        # be more aggressive
134                     options.fusb_block_size = gr.prefs().get_long('fusb', 'rt_block_size', 1024)
135                     options.fusb_nblocks    = gr.prefs().get_long('fusb', 'rt_nblocks', 16)
136                 else:
137                     options.fusb_block_size = gr.prefs().get_long('fusb', 'block_size', 4096)
138                     options.fusb_nblocks    = gr.prefs().get_long('fusb', 'nblocks', 16)
139     
140         #print "fusb_block_size =", options.fusb_block_size
141         #print "fusb_nblocks    =", options.fusb_nblocks
142
143         # build graph
144         
145         self.u = usrp.source_c(fusb_block_size=options.fusb_block_size,
146                                fusb_nblocks=options.fusb_nblocks)
147
148
149         adc_rate = self.u.adc_rate()                # 64 MS/s
150         usrp_decim = options.decim
151         self.u.set_decim_rate(usrp_decim)
152         usrp_rate = adc_rate / usrp_decim
153
154         self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
155         self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec)
156         print "Using RX d'board %s" % (self.subdev.side_and_name(),)
157
158
159         s2v = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size)
160
161         mywindow = window.blackmanharris(self.fft_size)
162         fft = gr.fft_vcc(self.fft_size, True, mywindow)
163         power = 0
164         for tap in mywindow:
165             power += tap*tap
166             
167         c2mag = gr.complex_to_mag_squared(self.fft_size)
168
169         # FIXME the log10 primitive is dog slow
170         log = gr.nlog10_ff(10, self.fft_size,
171                            -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size))
172                 
173         # Set the freq_step to 75% of the actual data throughput.
174         # This allows us to discard the bins on both ends of the spectrum.
175
176         self.freq_step = 0.75 * usrp_rate
177         self.min_center_freq = self.min_freq + self.freq_step/2
178         nsteps = math.ceil((self.max_freq - self.min_freq) / self.freq_step)
179         self.max_center_freq = self.min_center_freq + (nsteps * self.freq_step)
180
181         self.next_freq = self.min_center_freq
182         
183         tune_delay  = max(0, int(round(options.tune_delay * usrp_rate / self.fft_size)))  # in fft_frames
184         dwell_delay = max(1, int(round(options.dwell_delay * usrp_rate / self.fft_size))) # in fft_frames
185
186         self.msgq = gr.msg_queue(16)
187         self._tune_callback = tune(self)        # hang on to this to keep it from being GC'd
188         stats = gr.bin_statistics_f(self.fft_size, self.msgq,
189                                     self._tune_callback, tune_delay, dwell_delay)
190
191         # FIXME leave out the log10 until we speed it up
192         #self.connect(self.u, s2v, fft, c2mag, log, stats)
193         self.connect(self.u, s2v, fft, c2mag, stats)
194
195         if options.gain is None:
196             # if no gain was specified, use the mid-point in dB
197             g = self.subdev.gain_range()
198             options.gain = float(g[0]+g[1])/2
199
200         self.set_gain(options.gain)
201         print "gain =", options.gain
202
203
204     def set_next_freq(self):
205         target_freq = self.next_freq
206         self.next_freq = self.next_freq + self.freq_step
207         if self.next_freq >= self.max_center_freq:
208             self.next_freq = self.min_center_freq
209
210         if not self.set_freq(target_freq):
211             print "Failed to set frequency to", target_freq
212
213         return target_freq
214                           
215
216     def set_freq(self, target_freq):
217         """
218         Set the center frequency we're interested in.
219
220         @param target_freq: frequency in Hz
221         @rypte: bool
222
223         Tuning is a two step process.  First we ask the front-end to
224         tune as close to the desired frequency as it can.  Then we use
225         the result of that operation and our target_frequency to
226         determine the value for the digital down converter.
227         """
228         return self.u.tune(0, self.subdev, target_freq)
229
230
231     def set_gain(self, gain):
232         self.subdev.set_gain(gain)
233
234
235 def main_loop(tb):
236     while 1:
237
238         # Get the next message sent from the C++ code (blocking call).
239         # It contains the center frequency and the mag squared of the fft
240         m = parse_msg(tb.msgq.delete_head())
241
242         # Print center freq so we know that something is happening...
243         print m.center_freq
244
245         # FIXME do something useful with the data...
246         
247         # m.data are the mag_squared of the fft output (they are in the
248         # standard order.  I.e., bin 0 == DC.)
249         # You'll probably want to do the equivalent of "fftshift" on them
250         # m.raw_data is a string that contains the binary floats.
251         # You could write this as binary to a file.
252
253     
254 if __name__ == '__main__':
255     tb = my_top_block()
256     try:
257         tb.start()              # start executing flow graph in another thread...
258         main_loop(tb)
259         
260     except KeyboardInterrupt:
261         pass