usrp TV Tuner version 3 added
[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         nsteps = math.ceil((self.max_freq - self.min_freq) / self.freq_step)
160         self.max_center_freq = self.min_center_freq + (nsteps * self.freq_step)
161
162         self.next_freq = self.min_center_freq
163         
164         tune_delay  = max(0, int(round(options.tune_delay * usrp_rate / self.fft_size)))  # in fft_frames
165         dwell_delay = max(1, int(round(options.dwell_delay * usrp_rate / self.fft_size))) # in fft_frames
166
167         self.msgq = gr.msg_queue(16)
168         self._tune_callback = tune(self)        # hang on to this to keep it from being GC'd
169         stats = gr.bin_statistics_f(self.fft_size, self.msgq,
170                                     self._tune_callback, tune_delay, dwell_delay)
171
172         # FIXME leave out the log10 until we speed it up
173         #self.connect(self.u, s2v, fft, c2mag, log, stats)
174         self.connect(self.u, s2v, fft, c2mag, stats)
175
176         if options.gain is None:
177             # if no gain was specified, use the mid-point in dB
178             g = self.subdev.gain_range()
179             options.gain = float(g[0]+g[1])/2
180
181         self.set_gain(options.gain)
182         print "gain =", options.gain
183
184
185     def set_next_freq(self):
186         target_freq = self.next_freq
187         self.next_freq = self.next_freq + self.freq_step
188         if self.next_freq >= self.max_center_freq:
189             self.next_freq = self.min_center_freq
190
191         if not self.set_freq(target_freq):
192             print "Failed to set frequency to", target_freq
193
194         return target_freq
195                           
196
197     def set_freq(self, target_freq):
198         """
199         Set the center frequency we're interested in.
200
201         @param target_freq: frequency in Hz
202         @rypte: bool
203
204         Tuning is a two step process.  First we ask the front-end to
205         tune as close to the desired frequency as it can.  Then we use
206         the result of that operation and our target_frequency to
207         determine the value for the digital down converter.
208         """
209         return self.u.tune(0, self.subdev, target_freq)
210
211
212     def set_gain(self, gain):
213         self.subdev.set_gain(gain)
214
215
216 def main_loop(fg):
217     while 1:
218
219         # Get the next message sent from the C++ code (blocking call).
220         # It contains the center frequency and the mag squared of the fft
221         m = parse_msg(fg.msgq.delete_head())
222
223         # Print center freq so we know that something is happening...
224         print m.center_freq
225
226         # FIXME do something useful with the data...
227         
228         # m.data are the mag_squared of the fft output (they are in the
229         # standard order.  I.e., bin 0 == DC.)
230         # You'll probably want to do the equivalent of "fftshift" on them
231         # m.raw_data is a string that contains the binary floats.
232         # You could write this as binary to a file.
233
234     
235 if __name__ == '__main__':
236     fg = my_graph()
237     try:
238         fg.start()              # start executing flow graph in another thread...
239         main_loop(fg)
240         
241     except KeyboardInterrupt:
242         pass