1 | #!/usr/bin/env python |
---|
2 | # |
---|
3 | # Copyright 2005,2006,2007,2009 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, blks2 |
---|
24 | ############## imports specific to spectrum sense ####################### |
---|
25 | from gnuradio import optfir, window |
---|
26 | from gnuradio import audio |
---|
27 | from gnuradio.eng_option import eng_option |
---|
28 | from optparse import OptionParser |
---|
29 | from usrpm import usrp_dbid |
---|
30 | import math |
---|
31 | import struct |
---|
32 | ######################################################################### |
---|
33 | from gnuradio import usrp |
---|
34 | from gnuradio import eng_notation |
---|
35 | import copy |
---|
36 | import sys |
---|
37 | from numpy import random |
---|
38 | # from current dir |
---|
39 | from pick_bitrate import pick_rx_bitrate |
---|
40 | import usrp_options |
---|
41 | import random |
---|
42 | |
---|
43 | # ///////////////////////////////////////////////////////////////////////////// |
---|
44 | # receive path |
---|
45 | # ///////////////////////////////////////////////////////////////////////////// |
---|
46 | |
---|
47 | class receive_path(gr.hier_block2): |
---|
48 | def __init__(self, demod_class, rx_callback, options): |
---|
49 | |
---|
50 | gr.hier_block2.__init__(self, "receive_path", |
---|
51 | gr.io_signature(0, 0, 0), # Input signature |
---|
52 | gr.io_signature(0, 0, 0)) # Output signature |
---|
53 | |
---|
54 | options = copy.copy(options) # make a copy so we can destructively modify |
---|
55 | |
---|
56 | self._verbose = options.verbose |
---|
57 | self._rx_freq = options.rx_freq # receiver's center frequency |
---|
58 | self._rx_gain = options.rx_gain # receiver's gain |
---|
59 | self._bitrate = options.bitrate # desired bit rate |
---|
60 | self._decim = options.decim # Decimating rate for the USRP (prelim) |
---|
61 | self._samples_per_symbol = options.samples_per_symbol # desired samples/symbol |
---|
62 | |
---|
63 | self._rx_callback = rx_callback # this callback is fired when there's a packet available |
---|
64 | self._demod_class = demod_class # the demodulator_class we're using |
---|
65 | |
---|
66 | if self._rx_freq is None: |
---|
67 | sys.stderr.write("-f FREQ or --freq FREQ or --rx-freq FREQ must be specified\n") |
---|
68 | raise SystemExit |
---|
69 | |
---|
70 | # Set up USRP source; also adjusts decim, samples_per_symbol, and bitrate |
---|
71 | self._setup_usrp_source(options) |
---|
72 | |
---|
73 | if options.show_rx_gain_range: |
---|
74 | print "Rx Gain Range: minimum = %g, maximum = %g, step size = %g"%tuple(self.u.gain_range()) |
---|
75 | |
---|
76 | self.set_gain(options.rx_gain) |
---|
77 | |
---|
78 | # Set RF frequency |
---|
79 | ok = self.set_freq(self._rx_freq) |
---|
80 | if not ok: |
---|
81 | print "Failed to set Rx frequency to %s" % (eng_notation.num_to_str(self._rx_freq)) |
---|
82 | raise ValueError, eng_notation.num_to_str(self._rx_freq) |
---|
83 | |
---|
84 | # copy the final answers back into options for use by demodulator |
---|
85 | options.samples_per_symbol = self._samples_per_symbol |
---|
86 | options.bitrate = self._bitrate |
---|
87 | options.decim = self._decim |
---|
88 | |
---|
89 | # Get demod_kwargs |
---|
90 | demod_kwargs = self._demod_class.extract_kwargs_from_options(options) |
---|
91 | |
---|
92 | # Design filter to get actual channel we want |
---|
93 | sw_decim = 1 |
---|
94 | chan_coeffs = gr.firdes.low_pass (1.0, # gain |
---|
95 | sw_decim * self._samples_per_symbol, # sampling rate |
---|
96 | 1.0, # midpoint of trans. band |
---|
97 | 0.5, # width of trans. band |
---|
98 | gr.firdes.WIN_HANN) # filter type |
---|
99 | |
---|
100 | # Decimating channel filter |
---|
101 | # complex in and out, float taps |
---|
102 | self.chan_filt = gr.fft_filter_ccc(sw_decim, chan_coeffs) |
---|
103 | #self.chan_filt = gr.fir_filter_ccf(sw_decim, chan_coeffs) |
---|
104 | |
---|
105 | # receiver |
---|
106 | self.packet_receiver = \ |
---|
107 | blks2.demod_pkts(self._demod_class(**demod_kwargs), |
---|
108 | access_code=None, |
---|
109 | callback=self._rx_callback, |
---|
110 | threshold=-1) |
---|
111 | |
---|
112 | # Carrier Sensing Blocks |
---|
113 | alpha = 0.001 |
---|
114 | thresh = 30 # in dB, will have to adjust |
---|
115 | |
---|
116 | if options.log_rx_power == True: |
---|
117 | self.probe = gr.probe_avg_mag_sqrd_cf(thresh,alpha) |
---|
118 | self.power_sink = gr.file_sink(gr.sizeof_float, "rxpower.dat") |
---|
119 | self.connect(self.chan_filt, self.probe, self.power_sink) |
---|
120 | else: |
---|
121 | self.probe = gr.probe_avg_mag_sqrd_c(thresh,alpha) |
---|
122 | self.connect(self.chan_filt, self.probe) |
---|
123 | |
---|
124 | # Display some information about the setup |
---|
125 | if self._verbose: |
---|
126 | self._print_verbage() |
---|
127 | |
---|
128 | #################################spectrum sense specific code############################# |
---|
129 | |
---|
130 | |
---|
131 | self.min_freq = 462.4825e6 # setting min and max frequency inside the init rather than taking it from the command line |
---|
132 | self.max_freq = 462.6425e6 |
---|
133 | |
---|
134 | if self.min_freq > self.max_freq: |
---|
135 | self.min_freq, self.max_freq = self.max_freq, self.min_freq # swap them |
---|
136 | |
---|
137 | self.fft_size = 512 |
---|
138 | |
---|
139 | |
---|
140 | #if not options.real_time: |
---|
141 | # realtime = False |
---|
142 | |
---|
143 | #else: |
---|
144 | # # Attempt to enable realtime scheduling |
---|
145 | # r = gr.enable_realtime_scheduling() |
---|
146 | # if r == gr.RT_OK: |
---|
147 | # realtime = True |
---|
148 | # else: |
---|
149 | # realtime = False |
---|
150 | # print "Note: failed to enable realtime scheduling" |
---|
151 | |
---|
152 | # If the user hasn't set the fusb_* parameters on the command line, |
---|
153 | # pick some values that will reduce latency. |
---|
154 | |
---|
155 | #if 1: |
---|
156 | # if options.fusb_block_size == 0 and options.fusb_nblocks == 0: |
---|
157 | # if realtime: # be more aggressive |
---|
158 | # options.fusb_block_size = gr.prefs().get_long('fusb', 'rt_block_size', 1024) |
---|
159 | # options.fusb_nblocks = gr.prefs().get_long('fusb', 'rt_nblocks', 16) |
---|
160 | # else: |
---|
161 | # options.fusb_block_size = gr.prefs().get_long('fusb', 'block_size', 4096) |
---|
162 | # options.fusb_nblocks = gr.prefs().get_long('fusb', 'nblocks', 16) |
---|
163 | |
---|
164 | #print "fusb_block_size =", options.fusb_block_size |
---|
165 | #print "fusb_nblocks =", options.fusb_nblocks |
---|
166 | |
---|
167 | # build graph |
---|
168 | |
---|
169 | #self.u = usrp_options.create_usrp_source(options) |
---|
170 | |
---|
171 | |
---|
172 | #adc_rate = self.u.adc_rate() # 64 MS/s |
---|
173 | #usrp_decim = options.decim |
---|
174 | #self.u.set_decim_rate(usrp_decim) |
---|
175 | usrp_rate = self.adc_rate / self._decim |
---|
176 | |
---|
177 | #self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec)) |
---|
178 | #self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec) |
---|
179 | #print "Using RX d'board %s" % (self.subdev.side_and_name(),) |
---|
180 | |
---|
181 | |
---|
182 | |
---|
183 | s2v = gr.stream_to_vector(gr.sizeof_gr_complex, self.fft_size) |
---|
184 | |
---|
185 | mywindow = window.blackmanharris(self.fft_size) |
---|
186 | fft = gr.fft_vcc(self.fft_size, True, mywindow) |
---|
187 | power = 0 |
---|
188 | for tap in mywindow: |
---|
189 | power += tap*tap |
---|
190 | |
---|
191 | |
---|
192 | c2mag = gr.complex_to_mag_squared(self.fft_size) |
---|
193 | print "print c2mag ",c2mag,"\n" |
---|
194 | # FIXME the log10 primitive is dog slow |
---|
195 | log = gr.nlog10_ff(10, self.fft_size, |
---|
196 | -20*math.log10(self.fft_size)-10*math.log10(power/self.fft_size)) |
---|
197 | |
---|
198 | # Set the freq_step to 75% of the actual data throughput. |
---|
199 | # This allows us to discard the bins on both ends of the spectrum. |
---|
200 | |
---|
201 | self.freq_step = 0.75 * usrp_rate |
---|
202 | self.min_center_freq = self.min_freq + self.freq_step/2 |
---|
203 | nsteps = math.ceil((self.max_freq - self.min_freq) / self.freq_step) |
---|
204 | self.max_center_freq = self.min_center_freq + (nsteps * self.freq_step) |
---|
205 | |
---|
206 | self.next_freq = self.min_center_freq |
---|
207 | tune_delay = 1e-3 |
---|
208 | dwell_delay = 1e-2 |
---|
209 | tune_delay = max(0, int(round(tune_delay * usrp_rate / self.fft_size))) # in fft_frames |
---|
210 | dwell_delay = max(1, int(round(dwell_delay * usrp_rate / self.fft_size))) # in fft_frames |
---|
211 | |
---|
212 | self.msgq = gr.msg_queue(16) |
---|
213 | self._tune_callback = tune(self) # hang on to this to keep it from being GC'd |
---|
214 | |
---|
215 | stats = gr.bin_statistics_f(self.fft_size, self.msgq, |
---|
216 | self._tune_callback, tune_delay, dwell_delay) |
---|
217 | |
---|
218 | |
---|
219 | |
---|
220 | # FIXME leave out the log10 until we speed it up |
---|
221 | #self.connect(self.u, s2v, fft, c2mag, log, stats) |
---|
222 | #self.connect(self.u, s2v, fft, c2mag, stats) |
---|
223 | |
---|
224 | #if options.gain is None: |
---|
225 | # # if no gain was specified, use the mid-point in dB |
---|
226 | # g = self.subdev.gain_range() |
---|
227 | # options.gain = float(g[0]+g[1])/2 |
---|
228 | |
---|
229 | #self.set_gain(options.gain) |
---|
230 | #print "gain =", options.gain |
---|
231 | |
---|
232 | ########################################################################################## |
---|
233 | |
---|
234 | self.connect(self.u, self.chan_filt, self.packet_receiver) |
---|
235 | self.connect(self.u, s2v, fft, c2mag, stats) |
---|
236 | |
---|
237 | def _setup_usrp_source(self, options): |
---|
238 | |
---|
239 | self.u = usrp_options.create_usrp_source(options) |
---|
240 | self.adc_rate = self.u.adc_rate() |
---|
241 | |
---|
242 | # derive values of bitrate, samples_per_symbol, and decim from desired info |
---|
243 | (self._bitrate, self._samples_per_symbol, self._decim) = \ |
---|
244 | pick_rx_bitrate(self._bitrate, self._demod_class.bits_per_symbol(), \ |
---|
245 | self._samples_per_symbol, self._decim, self.adc_rate) |
---|
246 | |
---|
247 | self.u.set_decim(self._decim) |
---|
248 | |
---|
249 | def set_freq(self, target_freq): |
---|
250 | """ |
---|
251 | Set the center frequency we're interested in. |
---|
252 | |
---|
253 | @param target_freq: frequency in Hz |
---|
254 | @rypte: bool |
---|
255 | |
---|
256 | Tuning is a two step process. First we ask the front-end to |
---|
257 | tune as close to the desired frequency as it can. Then we use |
---|
258 | the result of that operation and our target_frequency to |
---|
259 | determine the value for the digital up converter. |
---|
260 | """ |
---|
261 | return self.u.set_center_freq(target_freq) |
---|
262 | |
---|
263 | def set_gain(self, gain): |
---|
264 | """ |
---|
265 | Sets the analog gain in the USRP |
---|
266 | """ |
---|
267 | return self.u.set_gain(gain) |
---|
268 | |
---|
269 | def bitrate(self): |
---|
270 | return self._bitrate |
---|
271 | |
---|
272 | def samples_per_symbol(self): |
---|
273 | return self._samples_per_symbol |
---|
274 | |
---|
275 | def decim(self): |
---|
276 | return self._decim |
---|
277 | |
---|
278 | def carrier_sensed(self): |
---|
279 | """ |
---|
280 | Return True if we think carrier is present. |
---|
281 | """ |
---|
282 | #return self.probe.level() > X |
---|
283 | return self.probe.unmuted() |
---|
284 | |
---|
285 | def carrier_threshold(self): |
---|
286 | """ |
---|
287 | Return current setting in dB. |
---|
288 | """ |
---|
289 | return self.probe.threshold() |
---|
290 | |
---|
291 | def set_carrier_threshold(self, threshold_in_db): |
---|
292 | """ |
---|
293 | Set carrier threshold. |
---|
294 | |
---|
295 | @param threshold_in_db: set detection threshold |
---|
296 | @type threshold_in_db: float (dB) |
---|
297 | """ |
---|
298 | self.probe.set_threshold(threshold_in_db) |
---|
299 | |
---|
300 | @staticmethod |
---|
301 | def add_options(normal, expert): |
---|
302 | """ |
---|
303 | Adds receiver-specific options to the Options Parser |
---|
304 | """ |
---|
305 | add_freq_option(normal) |
---|
306 | if not normal.has_option("--bitrate"): |
---|
307 | normal.add_option("-r", "--bitrate", type="eng_float", default=None, |
---|
308 | help="specify bitrate. samples-per-symbol and interp/decim will be derived.") |
---|
309 | usrp_options.add_rx_options(normal, expert) |
---|
310 | normal.add_option("-v", "--verbose", action="store_true", default=False) |
---|
311 | expert.add_option("-S", "--samples-per-symbol", type="int", default=None, |
---|
312 | help="set samples/symbol [default=%default]") |
---|
313 | expert.add_option("", "--rx-freq", type="eng_float", default=None, |
---|
314 | help="set Rx frequency to FREQ [default=%default]", metavar="FREQ") |
---|
315 | expert.add_option("", "--log", action="store_true", default=False, |
---|
316 | help="Log all parts of flow graph to files (CAUTION: lots of data)") |
---|
317 | expert.add_option("", "--log-rx-power", action="store_true", default=False, |
---|
318 | help="Log receive signal power to file (CAUTION: lots of data)") |
---|
319 | |
---|
320 | def set_next_freq(self): |
---|
321 | target_freq = self.next_freq |
---|
322 | self.next_freq = self.next_freq + self.freq_step |
---|
323 | if self.next_freq >= self.max_center_freq: |
---|
324 | self.next_freq = self.min_center_freq |
---|
325 | |
---|
326 | if not self.set_freq(target_freq): |
---|
327 | print "Failed to set frequency to", target_freq |
---|
328 | |
---|
329 | return target_freq |
---|
330 | |
---|
331 | def _print_verbage(self): |
---|
332 | """ |
---|
333 | Prints information about the receive path |
---|
334 | """ |
---|
335 | print "\nReceive Path:" |
---|
336 | print "USRP %s" % (self.u,) |
---|
337 | print "Rx gain: %g" % (self.u.gain(),) |
---|
338 | print "modulation: %s" % (self._demod_class.__name__) |
---|
339 | print "bitrate: %sb/s" % (eng_notation.num_to_str(self._bitrate)) |
---|
340 | print "samples/symbol: %3d" % (self._samples_per_symbol) |
---|
341 | print "decim: %3d" % (self._decim) |
---|
342 | print "Rx Frequency: %s" % (eng_notation.num_to_str(self._rx_freq)) |
---|
343 | |
---|
344 | #Getting spectrum information |
---|
345 | #Arguments... threshold - Minimum power equivalent available in spectrum |
---|
346 | # trials - Fft frames...should be 1 |
---|
347 | # exceed count - Number of times threshold is exceeded...useless |
---|
348 | |
---|
349 | def get_spec_stats(self,threshold,trials): |
---|
350 | power_sum = 0 #sum of powers(each power value is determined by adding the 'fft square' points..these fft square points are essentially points from the PSD curve...and adding them gives us the power contained in the spectrum) |
---|
351 | counter = 0 |
---|
352 | #exceed = 0 |
---|
353 | while counter < trials: |
---|
354 | # Get the next message sent from the C++ code (blocking call). |
---|
355 | # It contains the center frequency and the mag squared of the fft |
---|
356 | m = parse_msg(self.msgq.delete_head()) |
---|
357 | #print "printing mag sq of fft ",sum(m.data),"\n" |
---|
358 | power_sum = power_sum + sum(m.data) |
---|
359 | #if sum(m.data) > threshold |
---|
360 | # exceed+=1 |
---|
361 | #print sum(m.data),"\n" |
---|
362 | # Print center freq so we know that something is happening... |
---|
363 | #print m.center_freq |
---|
364 | counter +=1 |
---|
365 | # FIXME do something useful with the data... |
---|
366 | |
---|
367 | |
---|
368 | # m.data are the mag_squared of the fft output (they are in the |
---|
369 | # standard order. I.e., bin 0 == DC.) |
---|
370 | # You'll probably want to do the equivalent of "fftshift" on them |
---|
371 | # m.raw_data is a string that contains the binary floats. |
---|
372 | # You could write this as binary to a file. |
---|
373 | print "here" |
---|
374 | avg_power = power_sum/trials |
---|
375 | #ch = int(random.choice('17')) |
---|
376 | if avg_power >= threshold: |
---|
377 | return True |
---|
378 | #return 1,ch |
---|
379 | else: |
---|
380 | return False |
---|
381 | #return 0,"no" |
---|
382 | #print "printing average power ",avg_power,"\n" |
---|
383 | #if exceed >= exceed_count: |
---|
384 | # return avg_power,True |
---|
385 | # return avg_power,True #True = primary user is present at this frequency |
---|
386 | #else |
---|
387 | # return avg_power,False |
---|
388 | # return avg_power,False #False = primary user is NOT present at this frequency |
---|
389 | |
---|
390 | def add_freq_option(parser): |
---|
391 | """ |
---|
392 | Hackery that has the -f / --freq option set both tx_freq and rx_freq |
---|
393 | """ |
---|
394 | def freq_callback(option, opt_str, value, parser): |
---|
395 | parser.values.rx_freq = value |
---|
396 | parser.values.tx_freq = value |
---|
397 | |
---|
398 | if not parser.has_option('--freq'): |
---|
399 | parser.add_option('-f', '--freq', type="eng_float", |
---|
400 | action="callback", callback=freq_callback, |
---|
401 | help="set Tx and/or Rx frequency to FREQ [default=%default]", |
---|
402 | metavar="FREQ") |
---|
403 | |
---|
404 | |
---|
405 | ##################### Spectrum sense specific classes################################## |
---|
406 | |
---|
407 | class tune(gr.feval_dd): |
---|
408 | """ |
---|
409 | This class allows C++ code to callback into python. |
---|
410 | """ |
---|
411 | def __init__(self, tb): |
---|
412 | gr.feval_dd.__init__(self) |
---|
413 | self.tb = tb |
---|
414 | |
---|
415 | def eval(self, ignore): |
---|
416 | """ |
---|
417 | This method is called from gr.bin_statistics_f when it wants to change |
---|
418 | the center frequency. This method tunes the front end to the new center |
---|
419 | frequency, and returns the new frequency as its result. |
---|
420 | """ |
---|
421 | try: |
---|
422 | # We use this try block so that if something goes wrong from here |
---|
423 | # down, at least we'll have a prayer of knowing what went wrong. |
---|
424 | # Without this, you get a very mysterious: |
---|
425 | # |
---|
426 | # terminate called after throwing an instance of 'Swig::DirectorMethodException' |
---|
427 | # Aborted |
---|
428 | # |
---|
429 | # message on stderr. Not exactly helpful ;) |
---|
430 | |
---|
431 | new_freq = self.tb.set_next_freq() |
---|
432 | return new_freq |
---|
433 | |
---|
434 | except Exception, e: |
---|
435 | print "tune: Exception: ", e |
---|
436 | |
---|
437 | |
---|
438 | class parse_msg(object): |
---|
439 | def __init__(self, msg): |
---|
440 | self.center_freq = msg.arg1() |
---|
441 | self.vlen = int(msg.arg2()) |
---|
442 | assert(msg.length() == self.vlen * gr.sizeof_float) |
---|
443 | |
---|
444 | # FIXME consider using Numarray or NumPy vector |
---|
445 | t = msg.to_string() |
---|
446 | self.raw_data = t |
---|
447 | self.data = struct.unpack('%df' % (self.vlen,), t) |
---|
448 | |
---|
449 | |
---|
450 | ######################################################################################## |
---|