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