Updated FSF address in all files. Fixes ticket:51
[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             self.subdev.set_bw((self.u.adc_freq() / self.u.decim_rate())/2)
294
295         # Tell calibrator our declination as well
296         calib_set_decln(self.decln)
297
298         # Start the timer for the LMST display
299         self.lmst_timer.Start(1000)
300
301
302     def _set_status_msg(self, msg):
303         self.frame.GetStatusBar().SetStatusText(msg, 0)
304
305     def _build_gui(self, vbox):
306
307         def _form_set_freq(kv):
308             return self.set_freq(kv['freq'])
309
310         def _form_set_decln(kv):
311             return self.set_decln(kv['decln'])
312
313         # Position the FFT display
314         vbox.Add(self.scope.win, 15, wx.EXPAND)
315
316         # Position the Total-power stripchart
317         vbox.Add(self.chart.win, 15, wx.EXPAND)
318         
319         # add control area at the bottom
320         self.myform = myform = form.form()
321         hbox = wx.BoxSizer(wx.HORIZONTAL)
322         hbox.Add((7,0), 0, wx.EXPAND)
323         vbox1 = wx.BoxSizer(wx.VERTICAL)
324         myform['freq'] = form.float_field(
325             parent=self.panel, sizer=vbox1, label="Center freq", weight=1,
326             callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
327
328         vbox1.Add((4,0), 0, 0)
329
330         myform['lmst_high'] = form.static_text_field(
331             parent=self.panel, sizer=vbox1, label="Current LMST", weight=1)
332         vbox1.Add((4,0), 0, 0)
333
334         myform['spec_data'] = form.static_text_field(
335             parent=self.panel, sizer=vbox1, label="Spectral Cursor", weight=1)
336         vbox1.Add((4,0), 0, 0)
337
338         vbox2 = wx.BoxSizer(wx.VERTICAL)
339         g = self.subdev.gain_range()
340         myform['gain'] = form.slider_field(parent=self.panel, sizer=vbox2, label="RF Gain",
341                                            weight=1,
342                                            min=int(g[0]), max=int(g[1]),
343                                            callback=self.set_gain)
344
345         vbox2.Add((4,0), 0, 0)
346         myform['average'] = form.slider_field(parent=self.panel, sizer=vbox2, 
347                     label="Spectral Averaging (FFT frames)", weight=1, min=1, max=2000, callback=self.set_averaging)
348
349         vbox2.Add((4,0), 0, 0)
350
351         myform['integration'] = form.slider_field(parent=self.panel, sizer=vbox2,
352                label="Continuum Integration Time (sec)", weight=1, min=1, max=180, callback=self.set_integration)
353
354         vbox2.Add((4,0), 0, 0)
355         myform['decln'] = form.float_field(
356             parent=self.panel, sizer=vbox2, label="Current Declination", weight=1,
357             callback=myform.check_input_and_call(_form_set_decln))
358         vbox2.Add((4,0), 0, 0)
359
360         buttonbox = wx.BoxSizer(wx.HORIZONTAL)
361         if self.use_interfilt == True:
362             self.doit = form.button_with_callback(self.panel,
363                   label="Clear Interference List", 
364                   callback=self.clear_interferers)
365         if self.use_interfilt == True:
366             buttonbox.Add(self.doit, 0, wx.CENTER)
367         vbox.Add(buttonbox, 0, wx.CENTER)
368         hbox.Add(vbox1, 0, 0)
369         hbox.Add(vbox2, wx.ALIGN_RIGHT, 0)
370         vbox.Add(hbox, 0, wx.EXPAND)
371
372         self._build_subpanel(vbox)
373
374         self.lmst_timer = wx.PyTimer(self.lmst_timeout)
375         self.lmst_timeout()
376
377
378     def _build_subpanel(self, vbox_arg):
379         # build a secondary information panel (sometimes hidden)
380
381         # FIXME figure out how to have this be a subpanel that is always
382         # created, but has its visibility controlled by foo.Show(True/False)
383         
384         if not(self.show_debug_info):
385             return
386
387         panel = self.panel
388         vbox = vbox_arg
389         myform = self.myform
390
391         #panel = wx.Panel(self.panel, -1)
392         #vbox = wx.BoxSizer(wx.VERTICAL)
393
394         hbox = wx.BoxSizer(wx.HORIZONTAL)
395         hbox.Add((5,0), 0)
396         myform['decim'] = form.static_float_field(
397             parent=panel, sizer=hbox, label="Decim")
398
399         hbox.Add((5,0), 1)
400         myform['fs@usb'] = form.static_float_field(
401             parent=panel, sizer=hbox, label="Fs@USB")
402
403         hbox.Add((5,0), 1)
404         myform['dbname'] = form.static_text_field(
405             parent=panel, sizer=hbox)
406
407         hbox.Add((5,0), 1)
408         myform['baseband'] = form.static_float_field(
409             parent=panel, sizer=hbox, label="Analog BB")
410
411         hbox.Add((5,0), 1)
412         myform['ddc'] = form.static_float_field(
413             parent=panel, sizer=hbox, label="DDC")
414
415         hbox.Add((5,0), 0)
416         vbox.Add(hbox, 0, wx.EXPAND)
417
418         
419         
420     def set_freq(self, target_freq):
421         """
422         Set the center frequency we're interested in.
423
424         @param target_freq: frequency in Hz
425         @rypte: bool
426
427         Tuning is a two step process.  First we ask the front-end to
428         tune as close to the desired frequency as it can.  Then we use
429         the result of that operation and our target_frequency to
430         determine the value for the digital down converter.
431         """
432         #
433         # Everything except BASIC_RX should support usrp.tune()
434         #
435         if not (self.cardtype == usrp_dbid.BASIC_RX):
436             r = usrp.tune(self.u, 0, self.subdev, target_freq)
437         else:
438             r = self.u.set_rx_freq(0, target_freq)
439             f = self.u.rx_freq(0)
440             if abs(f-target_freq) > 2.0e3:
441                 r = 0
442         if r:
443             self.myform['freq'].set_value(target_freq)     # update displayed value
444             #
445             # Make sure calibrator knows our target freq
446             #
447
448             # Remember centerfreq---used for doppler calcs
449             delta = self.centerfreq - target_freq
450             self.centerfreq = target_freq
451             self.observing -= delta
452             self.scope.set_baseband_freq (self.observing)
453             calib_set_freq(self.observing)
454
455             # Clear interference list
456             self.clear_interferers()
457
458             self.myform['baseband'].set_value(r.baseband_freq)
459             self.myform['ddc'].set_value(r.dxc_freq)
460
461             return True
462
463         return False
464
465     def set_decln(self, dec):
466         self.decln = dec
467         self.myform['decln'].set_value(dec)     # update displayed value
468         calib_set_decln(dec)
469
470     def set_gain(self, gain):
471         self.myform['gain'].set_value(gain)     # update displayed value
472         self.subdev.set_gain(gain)
473
474         #
475         # Make sure calibrator knows our gain setting
476         #
477         calib_set_gain(gain)
478
479     def set_averaging(self, avval):
480         self.myform['average'].set_value(avval)
481         self.scope.set_avg_alpha(1.0/(avval))
482         calib_set_avg_alpha(avval)
483         self.scope.set_average(True)
484
485     def set_integration(self, integval):
486         self.integrator3.set_taps(1.0/integval)
487         self.myform['integration'].set_value(integval)
488
489         #
490         # Make sure calibrator knows our integration time
491         #
492         calib_set_integ(integval)
493
494     def lmst_timeout(self):
495          self.locality.date = ephem.now()
496          sidtime = self.locality.sidereal_time()
497          self.myform['lmst_high'].set_value(str(ephem.hours(sidtime)))
498
499     def xydfunc(self,xyv):
500         magn = int(log10(self.observing))
501         if (magn == 6 or magn == 7 or magn == 8):
502             magn = 6
503         dfreq = xyv[0] * pow(10.0,magn)
504         ratio = self.observing / dfreq
505         vs = 1.0 - ratio
506         vs *= 299792.0
507         if magn >= 9:
508            xhz = "Ghz"
509         elif magn >= 6:
510            xhz = "Mhz"
511         elif magn <= 5:
512            xhz =  "Khz"
513         s = "%.6f%s\n%.3fdB" % (xyv[0], xhz, xyv[1])
514         s2 = "\n%.3fkm/s" % vs
515         self.myform['spec_data'].set_value(s+s2)
516
517     def interference(self,x):
518         if self.use_interfilt == False:
519             return
520         magn = int(log10(self.observing))
521         dfreq = x * pow(10.0,magn)
522         delta = dfreq - self.observing
523         fincr = self.bw / len(self.intmap)
524         l = len(self.intmap)
525         if delta > 0:
526             offset = delta/fincr
527         else:
528             offset = (l) - int((abs(delta)/fincr))
529
530         offset = int(offset)
531
532         if offset >= len(self.intmap) or offset < 0:
533             print "interference offset is invalid--", offset
534             return
535
536         #
537         # Zero out the region around the selected interferer
538         #
539         self.intmap[offset-2] = complex (0.5, 0.0)
540         self.intmap[offset-1] = complex (0.25, 0.0)
541         self.intmap[offset] = complex (0.0, 0.0)
542         self.intmap[offset+1] = complex(0.25, 0.0)
543         self.intmap[offset+2] = complex(0.5, 0.0)
544
545         #
546         # Set new taps
547         #
548         tmp = FFT.inverse_fft(self.intmap)
549         self.interfilt.set_taps(tmp)
550
551     def clear_interf(self):
552          self.clear_interferers()
553
554     def clear_interferers(self):
555          for i in range(0,len(self.intmap)):
556              self.intmap[i] = complex(1.0,0.0)
557          tmp = FFT.inverse_fft(self.intmap)
558          if self.use_interfilt == True:
559              self.interfilt.set_taps(tmp)
560    
561
562
563     def toggle_cal(self):
564         if (self.calstate == True):
565           self.calstate = False
566           self.u.write_io(0,0,(1<<15))
567           self.calibrator.SetLabel("Calibration Source: Off")
568         else:
569           self.calstate = True
570           self.u.write_io(0,(1<<15),(1<<15))
571           self.calibrator.SetLabel("Calibration Source: On")
572
573     def toggle_annotation(self):
574         if (self.annotate_state == True):
575           self.annotate_state = False
576           self.annotation.SetLabel("Annotation: Off")
577         else:
578           self.annotate_state = True
579           self.annotation.SetLabel("Annotation: On")
580         calib_set_interesting(self.annotate_state)
581         
582
583 def main ():
584     app = stdgui.stdapp(app_flow_graph, "RADIO ASTRONOMY SPECTRAL/CONTINUUM RECEIVER: $Revision$", nstatus=1)
585     app.MainLoop()
586
587 if __name__ == '__main__':
588     main ()