Imported Upstream version 3.0
[debian/gnuradio] / gr-radio-astronomy / src / python / usrp_ra_receiver.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2004,2005 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 2, 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
24 from gnuradio import usrp
25 import usrp_dbid
26 from gnuradio import eng_notation
27 from gnuradio.eng_option import eng_option
28 from gnuradio.wxgui import stdgui, ra_fftsink, ra_stripchartsink, waterfallsink, form, slider
29 from optparse import OptionParser
30 import wx
31 import sys
32 from Numeric import *
33 import FFT
34 import ephem
35 from gnuradio.local_calibrator import *
36
37 class app_flow_graph(stdgui.gui_flow_graph):
38     def __init__(self, frame, panel, vbox, argv):
39         stdgui.gui_flow_graph.__init__(self)
40
41         self.frame = frame
42         self.panel = panel
43         
44         parser = OptionParser(option_class=eng_option)
45         parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=(0, 0),
46                           help="select USRP Rx side A or B (default=A)")
47         parser.add_option("-d", "--decim", type="int", default=16,
48                           help="set fgpa decimation rate to DECIM [default=%default]")
49         parser.add_option("-f", "--freq", type="eng_float", default=None,
50                           help="set frequency to FREQ", metavar="FREQ")
51         parser.add_option("-a", "--avg", type="eng_float", default=1.0,
52                 help="set spectral averaging alpha")
53         parser.add_option("-i", "--integ", type="eng_float", default=1.0,
54                 help="set integration time")
55         parser.add_option("-g", "--gain", type="eng_float", default=None,
56                           help="set gain in dB (default is midpoint)")
57         parser.add_option("-l", "--reflevel", type="eng_float", default=30.0,
58                           help="Set Total power reference level")
59         parser.add_option("-y", "--division", type="eng_float", default=0.5,
60                           help="Set Total power Y division size")
61         parser.add_option("-e", "--longitude", type="eng_float", default=-76.02,                          help="Set Observer Longitude")
62         parser.add_option("-c", "--latitude", type="eng_float", default=44.85,                          help="Set Observer Latitude")
63         parser.add_option("-o", "--observing", type="eng_float", default=0.0,
64                         help="Set observing frequency")
65         parser.add_option("-x", "--ylabel", default="dB", help="Y axis label") 
66         parser.add_option("-C", "--cfunc", default="default", help="Calibration function name") 
67         parser.add_option("-z", "--divbase", type="eng_float", default=0.025, help="Y Division increment base") 
68         parser.add_option("-v", "--stripsize", type="eng_float", default=2400, help="Size of stripchart, in 2Hz samples") 
69         parser.add_option("-F", "--fft_size", type="eng_float", default=1024, help="Size of FFT")
70
71         parser.add_option("-N", "--decln", type="eng_float", default=999.99, help="Observing declination")
72         parser.add_option("-I", "--interfilt", action="store_true", default=False)
73         parser.add_option("-X", "--prefix", default="./")
74         (options, args) = parser.parse_args()
75         if len(args) != 0:
76             parser.print_help()
77             sys.exit(1)
78
79         self.show_debug_info = True
80         
81         # build the graph
82
83         self.u = usrp.source_c(decim_rate=options.decim)
84         self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
85         self.cardtype = self.u.daughterboard_id(0)
86         # Set initial declination
87         self.decln = options.decln
88
89         # Turn off interference filter by default
90         self.use_interfilt = options.interfilt
91
92         # determine the daughterboard subdevice we're using
93         self.subdev = usrp.selected_subdev(self.u, options.rx_subdev_spec)
94
95         input_rate = self.u.adc_freq() / self.u.decim_rate()
96
97         tpstr="calib_"+options.cfunc+"_total_power"
98         sstr="calib_"+options.cfunc+"_fft"
99         self.tpcfunc=eval(tpstr)
100         self.scfunc=eval(sstr)
101
102         #
103         # Set prefix for data files
104         #
105         self.prefix = options.prefix
106         calib_set_prefix(self.prefix)
107
108         # Set up FFT display
109         self.scope = ra_fftsink.ra_fft_sink_c (self, panel, 
110            fft_size=int(options.fft_size), sample_rate=input_rate,
111            fft_rate=8, title="Spectral",  
112            cfunc=self.scfunc, xydfunc=self.xydfunc, interfunc=self.interference)
113
114         # Set up ephemeris data
115         self.locality = ephem.Observer()
116         self.locality.long = str(options.longitude)
117         self.locality.lat = str(options.latitude)
118
119         # Set up stripchart display
120         self.stripsize = int(options.stripsize)
121         self.chart = ra_stripchartsink.stripchart_sink_f (self, panel,
122             stripsize=self.stripsize,
123             title="Continuum",
124             xlabel="LMST Offset (Seconds)",
125             scaling=1.0, ylabel=options.ylabel,
126             divbase=options.divbase, cfunc=self.tpcfunc)
127
128         # Set center frequency
129         self.centerfreq = options.freq
130
131         # Set observing frequency (might be different from actual programmed
132         #    RF frequency)
133         if options.observing == 0.0:
134             self.observing = options.freq
135         else:
136             self.observing = options.observing
137
138         self.bw = input_rate
139
140         #
141         # Produce a default interference map
142         #  May not actually get used, unless --interfilt was specified
143         #
144         self.intmap = Numeric.zeros(256,Numeric.Complex64)
145         for i in range(0,len(self.intmap)):
146             self.intmap[i] = complex(1.0, 0.0)
147
148         # We setup the first two integrators to produce a fixed integration
149         # Down to 1Hz, with output at 1 samples/sec
150         N = input_rate/5000
151
152         # Second stage runs on decimated output of first
153         M = (input_rate/N)
154
155         # Create taps for first integrator
156         t = range(0,N-1)
157         tapsN = []
158         for i in t:
159              tapsN.append(1.0/N)
160
161         # Create taps for second integrator
162         t = range(0,M-1)
163         tapsM = []
164         for i in t:
165             tapsM.append(1.0/M)
166
167         #
168         # The 3rd integrator is variable, and user selectable at runtime
169         # This integrator doesn't decimate, but is used to set the
170         #  final integration time based on the constant 1Hz input samples
171         # The strip chart is fed at a constant 1Hz rate as a result
172         #
173
174         #
175         # Call constructors for receive chains
176         #
177
178         #
179         # This is the interference-zapping filter
180         #
181         # The GUI is used to set/clear inteference zones in
182         #   the filter.  The non-interfering zones are set to
183         #   1.0.
184         #
185         if 0:
186             self.interfilt = gr.fft_filter_ccc(1,self.intmap)
187             tmp = FFT.inverse_fft(self.intmap)
188             self.interfilt.set_taps(tmp)
189
190         # The three integrators--two FIR filters, and an IIR final filter
191         self.integrator1 = gr.fir_filter_fff (N, tapsN)
192         self.integrator2 = gr.fir_filter_fff (M, tapsM)
193         self.integrator3 = gr.single_pole_iir_filter_ff(1.0)
194
195         # Split complex USRP stream into a pair of floats
196         self.splitter = gr.complex_to_float (1);
197         self.toshort = gr.float_to_short();
198
199         # I squarer (detector)
200         self.multI = gr.multiply_ff();
201
202         # Q squarer (detector)
203         self.multQ = gr.multiply_ff();
204
205         # Adding squared I and Q to produce instantaneous signal power
206         self.adder = gr.add_ff();
207
208         #
209         # Start connecting configured modules in the receive chain
210         #
211
212         # Connect interference-filtered USRP input to selected scope function
213         if self.use_interfilt == True:
214             self.connect(self.u, self.interfilt, self.scope)
215
216             # Connect interference-filtered USRP to a complex->float splitter
217             self.connect(self.interfilt, self.splitter)
218
219         else:
220             self.connect(self.u, self.scope)
221             self.connect(self.u, self.splitter)
222
223         # Connect splitter outputs to multipliers
224         # First do I^2
225         self.connect((self.splitter, 0), (self.multI,0))
226         self.connect((self.splitter, 0), (self.multI,1))
227
228         # Then do Q^2
229         self.connect((self.splitter, 1), (self.multQ,0))
230         self.connect((self.splitter, 1), (self.multQ,1))
231
232         # Then sum the squares
233         self.connect(self.multI, (self.adder,0))
234         self.connect(self.multQ, (self.adder,1))
235
236         # Connect adder output to three-stages of FIR integrator
237         self.connect(self.adder, self.integrator1, 
238            self.integrator2, self.integrator3, self.chart)
239
240
241         self._build_gui(vbox)
242
243         # Make GUI agree with command-line
244         self.myform['integration'].set_value(int(options.integ))
245         self.myform['average'].set_value(int(options.avg))
246
247         # Make integrator agree with command line
248         self.set_integration(int(options.integ))
249
250         # Make spectral averager agree with command line
251         if options.avg != 1.0:
252             self.scope.set_avg_alpha(float(1.0/options.avg))
253             calib_set_avg_alpha(float(options.avg))
254             self.scope.set_average(True)
255
256
257         # Set division size
258         self.chart.set_y_per_div(options.division)
259
260         # Set reference(MAX) level
261         self.chart.set_ref_level(options.reflevel)
262
263         # set initial values
264
265         if options.gain is None:
266             # if no gain was specified, use the mid-point in dB
267             g = self.subdev.gain_range()
268             options.gain = float(g[0]+g[1])/2
269
270         if options.freq is None:
271             # if no freq was specified, use the mid-point
272             r = self.subdev.freq_range()
273             options.freq = float(r[0]+r[1])/2
274
275         # Set the initial gain control
276         self.set_gain(options.gain)
277
278         if not(self.set_freq(options.freq)):
279             self._set_status_msg("Failed to set initial frequency")
280
281         self.set_decln (self.decln)
282         calib_set_decln (self.decln)
283
284         self.myform['decim'].set_value(self.u.decim_rate())
285         self.myform['fs@usb'].set_value(self.u.adc_freq() / self.u.decim_rate())
286         self.myform['dbname'].set_value(self.subdev.name())
287
288         # Make sure calibrator knows what our bandwidth is
289         calib_set_bw(self.u.adc_freq() / self.u.decim_rate())
290
291         # Set analog baseband filtering, if DBS_RX
292         if self.cardtype == usrp_dbid.DBS_RX:
293             lbw = (self.u.adc_freq() / self.u.decim_rate()) / 2
294             if lbw < 1.0e6:
295                 lbw = 1.0e6
296             self.subdev.set_bw(lbw)
297
298         # Tell calibrator our declination as well
299         calib_set_decln(self.decln)
300
301         # Start the timer for the LMST display
302         self.lmst_timer.Start(1000)
303
304
305     def _set_status_msg(self, msg):
306         self.frame.GetStatusBar().SetStatusText(msg, 0)
307
308     def _build_gui(self, vbox):
309
310         def _form_set_freq(kv):
311             return self.set_freq(kv['freq'])
312
313         def _form_set_decln(kv):
314             return self.set_decln(kv['decln'])
315
316         # Position the FFT display
317         vbox.Add(self.scope.win, 15, wx.EXPAND)
318
319         # Position the Total-power stripchart
320         vbox.Add(self.chart.win, 15, wx.EXPAND)
321         
322         # add control area at the bottom
323         self.myform = myform = form.form()
324         hbox = wx.BoxSizer(wx.HORIZONTAL)
325         hbox.Add((7,0), 0, wx.EXPAND)
326         vbox1 = wx.BoxSizer(wx.VERTICAL)
327         myform['freq'] = form.float_field(
328             parent=self.panel, sizer=vbox1, label="Center freq", weight=1,
329             callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
330
331         vbox1.Add((4,0), 0, 0)
332
333         myform['lmst_high'] = form.static_text_field(
334             parent=self.panel, sizer=vbox1, label="Current LMST", weight=1)
335         vbox1.Add((4,0), 0, 0)
336
337         myform['spec_data'] = form.static_text_field(
338             parent=self.panel, sizer=vbox1, label="Spectral Cursor", weight=1)
339         vbox1.Add((4,0), 0, 0)
340
341         vbox2 = wx.BoxSizer(wx.VERTICAL)
342         g = self.subdev.gain_range()
343         myform['gain'] = form.slider_field(parent=self.panel, sizer=vbox2, label="RF Gain",
344                                            weight=1,
345                                            min=int(g[0]), max=int(g[1]),
346                                            callback=self.set_gain)
347
348         vbox2.Add((4,0), 0, 0)
349         myform['average'] = form.slider_field(parent=self.panel, sizer=vbox2, 
350                     label="Spectral Averaging (FFT frames)", weight=1, min=1, max=2000, callback=self.set_averaging)
351
352         vbox2.Add((4,0), 0, 0)
353
354         myform['integration'] = form.slider_field(parent=self.panel, sizer=vbox2,
355                label="Continuum Integration Time (sec)", weight=1, min=1, max=180, callback=self.set_integration)
356
357         vbox2.Add((4,0), 0, 0)
358         myform['decln'] = form.float_field(
359             parent=self.panel, sizer=vbox2, label="Current Declination", weight=1,
360             callback=myform.check_input_and_call(_form_set_decln))
361         vbox2.Add((4,0), 0, 0)
362
363         buttonbox = wx.BoxSizer(wx.HORIZONTAL)
364         if self.use_interfilt == True:
365             self.doit = form.button_with_callback(self.panel,
366                   label="Clear Interference List", 
367                   callback=self.clear_interferers)
368         if self.use_interfilt == True:
369             buttonbox.Add(self.doit, 0, wx.CENTER)
370         vbox.Add(buttonbox, 0, wx.CENTER)
371         hbox.Add(vbox1, 0, 0)
372         hbox.Add(vbox2, wx.ALIGN_RIGHT, 0)
373         vbox.Add(hbox, 0, wx.EXPAND)
374
375         self._build_subpanel(vbox)
376
377         self.lmst_timer = wx.PyTimer(self.lmst_timeout)
378         self.lmst_timeout()
379
380
381     def _build_subpanel(self, vbox_arg):
382         # build a secondary information panel (sometimes hidden)
383
384         # FIXME figure out how to have this be a subpanel that is always
385         # created, but has its visibility controlled by foo.Show(True/False)
386         
387         if not(self.show_debug_info):
388             return
389
390         panel = self.panel
391         vbox = vbox_arg
392         myform = self.myform
393
394         #panel = wx.Panel(self.panel, -1)
395         #vbox = wx.BoxSizer(wx.VERTICAL)
396
397         hbox = wx.BoxSizer(wx.HORIZONTAL)
398         hbox.Add((5,0), 0)
399         myform['decim'] = form.static_float_field(
400             parent=panel, sizer=hbox, label="Decim")
401
402         hbox.Add((5,0), 1)
403         myform['fs@usb'] = form.static_float_field(
404             parent=panel, sizer=hbox, label="Fs@USB")
405
406         hbox.Add((5,0), 1)
407         myform['dbname'] = form.static_text_field(
408             parent=panel, sizer=hbox)
409
410         hbox.Add((5,0), 1)
411         myform['baseband'] = form.static_float_field(
412             parent=panel, sizer=hbox, label="Analog BB")
413
414         hbox.Add((5,0), 1)
415         myform['ddc'] = form.static_float_field(
416             parent=panel, sizer=hbox, label="DDC")
417
418         hbox.Add((5,0), 0)
419         vbox.Add(hbox, 0, wx.EXPAND)
420
421         
422         
423     def set_freq(self, target_freq):
424         """
425         Set the center frequency we're interested in.
426
427         @param target_freq: frequency in Hz
428         @rypte: bool
429
430         Tuning is a two step process.  First we ask the front-end to
431         tune as close to the desired frequency as it can.  Then we use
432         the result of that operation and our target_frequency to
433         determine the value for the digital down converter.
434         """
435         #
436         # Everything except BASIC_RX should support usrp.tune()
437         #
438         if not (self.cardtype == usrp_dbid.BASIC_RX):
439             r = usrp.tune(self.u, 0, self.subdev, target_freq)
440         else:
441             r = self.u.set_rx_freq(0, target_freq)
442             f = self.u.rx_freq(0)
443             if abs(f-target_freq) > 2.0e3:
444                 r = 0
445         if r:
446             self.myform['freq'].set_value(target_freq)     # update displayed value
447             #
448             # Make sure calibrator knows our target freq
449             #
450
451             # Remember centerfreq---used for doppler calcs
452             delta = self.centerfreq - target_freq
453             self.centerfreq = target_freq
454             self.observing -= delta
455             self.scope.set_baseband_freq (self.observing)
456             calib_set_freq(self.observing)
457
458             # Clear interference list
459             self.clear_interferers()
460
461             self.myform['baseband'].set_value(r.baseband_freq)
462             self.myform['ddc'].set_value(r.dxc_freq)
463
464             return True
465
466         return False
467
468     def set_decln(self, dec):
469         self.decln = dec
470         self.myform['decln'].set_value(dec)     # update displayed value
471         calib_set_decln(dec)
472
473     def set_gain(self, gain):
474         self.myform['gain'].set_value(gain)     # update displayed value
475         self.subdev.set_gain(gain)
476
477         #
478         # Make sure calibrator knows our gain setting
479         #
480         calib_set_gain(gain)
481
482     def set_averaging(self, avval):
483         self.myform['average'].set_value(avval)
484         self.scope.set_avg_alpha(1.0/(avval))
485         calib_set_avg_alpha(avval)
486         self.scope.set_average(True)
487
488     def set_integration(self, integval):
489         self.integrator3.set_taps(1.0/integval)
490         self.myform['integration'].set_value(integval)
491
492         #
493         # Make sure calibrator knows our integration time
494         #
495         calib_set_integ(integval)
496
497     def lmst_timeout(self):
498          self.locality.date = ephem.now()
499          sidtime = self.locality.sidereal_time()
500          self.myform['lmst_high'].set_value(str(ephem.hours(sidtime)))
501
502     def xydfunc(self,xyv):
503         magn = int(log10(self.observing))
504         if (magn == 6 or magn == 7 or magn == 8):
505             magn = 6
506         dfreq = xyv[0] * pow(10.0,magn)
507         ratio = self.observing / dfreq
508         vs = 1.0 - ratio
509         vs *= 299792.0
510         if magn >= 9:
511            xhz = "Ghz"
512         elif magn >= 6:
513            xhz = "Mhz"
514         elif magn <= 5:
515            xhz =  "Khz"
516         s = "%.6f%s\n%.3fdB" % (xyv[0], xhz, xyv[1])
517         s2 = "\n%.3fkm/s" % vs
518         self.myform['spec_data'].set_value(s+s2)
519
520     def interference(self,x):
521         if self.use_interfilt == False:
522             return
523         magn = int(log10(self.observing))
524         dfreq = x * pow(10.0,magn)
525         delta = dfreq - self.observing
526         fincr = self.bw / len(self.intmap)
527         l = len(self.intmap)
528         if delta > 0:
529             offset = delta/fincr
530         else:
531             offset = (l) - int((abs(delta)/fincr))
532
533         offset = int(offset)
534
535         if offset >= len(self.intmap) or offset < 0:
536             print "interference offset is invalid--", offset
537             return
538
539         #
540         # Zero out the region around the selected interferer
541         #
542         self.intmap[offset-2] = complex (0.5, 0.0)
543         self.intmap[offset-1] = complex (0.25, 0.0)
544         self.intmap[offset] = complex (0.0, 0.0)
545         self.intmap[offset+1] = complex(0.25, 0.0)
546         self.intmap[offset+2] = complex(0.5, 0.0)
547
548         #
549         # Set new taps
550         #
551         tmp = FFT.inverse_fft(self.intmap)
552         self.interfilt.set_taps(tmp)
553
554     def clear_interf(self):
555          self.clear_interferers()
556
557     def clear_interferers(self):
558          for i in range(0,len(self.intmap)):
559              self.intmap[i] = complex(1.0,0.0)
560          tmp = FFT.inverse_fft(self.intmap)
561          if self.use_interfilt == True:
562              self.interfilt.set_taps(tmp)
563    
564
565
566     def toggle_cal(self):
567         if (self.calstate == True):
568           self.calstate = False
569           self.u.write_io(0,0,(1<<15))
570           self.calibrator.SetLabel("Calibration Source: Off")
571         else:
572           self.calstate = True
573           self.u.write_io(0,(1<<15),(1<<15))
574           self.calibrator.SetLabel("Calibration Source: On")
575
576     def toggle_annotation(self):
577         if (self.annotate_state == True):
578           self.annotate_state = False
579           self.annotation.SetLabel("Annotation: Off")
580         else:
581           self.annotate_state = True
582           self.annotation.SetLabel("Annotation: On")
583         calib_set_interesting(self.annotate_state)
584         
585
586 def main ():
587     app = stdgui.stdapp(app_flow_graph, "RADIO ASTRONOMY SPECTRAL/CONTINUUM RECEIVER: $Revision: 3602 $", nstatus=1)
588     app.MainLoop()
589
590 if __name__ == '__main__':
591     main ()