c2f06d7152d068f273669110b5861fc6567d6f3e
[debian/gnuradio] / gr-utils / src / python / qr_fft.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2004,2005,2007,2008,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.wxgui import forms
24 from gnuradio import gr, gru
25 from gnuradio import vrt
26 from gnuradio import eng_notation
27 from gnuradio.eng_option import eng_option
28 from gnuradio.wxgui import stdgui2, fftsink2, waterfallsink2, scopesink2, form, slider
29 from gnuradio.gr import pubsub
30 from optparse import OptionParser
31 import wx
32 import sys
33 import numpy
34 import time
35
36 class app_top_block(stdgui2.std_top_block, pubsub.pubsub):
37     def __init__(self, frame, panel, vbox, argv):
38         stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv)
39         pubsub.pubsub.__init__(self)
40         self.frame = frame
41         self.panel = panel
42         
43         parser = OptionParser(option_class=eng_option)
44         #parser.add_option("-e", "--interface", type="string", default="eth0",
45         #                  help="select Ethernet interface, default is eth0")
46         #parser.add_option("-m", "--mac-addr", type="string", default="",
47         #                  help="select USRP by MAC address, default is auto-select")
48         #parser.add_option("-A", "--antenna", default=None,
49         #                  help="select Rx Antenna (only on RFX-series boards)")
50         #parser.add_option("-d", "--decim", type="int", default=16,
51         #                  help="set fgpa decimation rate to DECIM [default=%default]")
52         #parser.add_option("-f", "--freq", type="eng_float", default=None,
53         #                  help="set frequency to FREQ", metavar="FREQ")
54         #parser.add_option("-g", "--gain", type="eng_float", default=None,
55         #                  help="set gain in dB (default is midpoint)")
56         parser.add_option("-W", "--waterfall", action="store_true", default=False,
57                           help="Enable waterfall display")
58         parser.add_option("-S", "--oscilloscope", action="store_true", default=False,
59                           help="Enable oscilloscope display")
60         parser.add_option("", "--avg-alpha", type="eng_float", default=1e-1,
61                           help="Set fftsink averaging factor, default=[%default]")
62         parser.add_option("", "--ref-scale", type="eng_float", default=1.0,
63                           help="Set dBFS=0dB input value, default=[%default]")
64         parser.add_option("--fft-size", type="int", default=1024,
65                           help="Set number of FFT bins [default=%default]")
66         parser.add_option("--samples-per-pkt", type="int", default=0,
67                           help="Set number of SAMPLES-PER-PKT [default=%default]")
68         parser.add_option("", "--ip-addr", type="string", default="192.168.10.2",
69                           help="IP address default=[%default]")
70         (options, args) = parser.parse_args()
71         if len(args) != 0:
72             parser.print_help()
73             sys.exit(1)
74         self.options = options
75         self.show_debug_info = True
76         
77         self.u = vrt.quadradio_source_32fc(options.ip_addr,
78                                            int(62.5e6), options.samples_per_pkt)
79         #self.u.set_decim(options.decim)
80         
81         #input_rate = self.u.adc_rate() / self.u.decim()
82         input_rate = int(120e6/4)
83         
84         if options.waterfall:
85             self.scope = \
86               waterfallsink2.waterfall_sink_c (panel, fft_size=1024, sample_rate=input_rate)
87         elif options.oscilloscope:
88             self.scope = scopesink2.scope_sink_c(panel, sample_rate=input_rate)
89         else:
90             self.scope = fftsink2.fft_sink_c (panel,
91                                               fft_size=options.fft_size,
92                                               sample_rate=input_rate, 
93                                               ref_scale=options.ref_scale,
94                                               ref_level=20.0,
95                                               y_divs = 12,
96                                               avg_alpha=options.avg_alpha)
97
98         self.connect(self.u, self.scope)
99
100         self._build_gui(vbox)
101         self._setup_events()
102         
103         # set initial values
104
105         #if options.gain is None:
106         #    # if no gain was specified, use the mid-point in dB
107         #    g = self.u.gain_range()
108         #    options.gain = float(g[0]+g[1])/2
109
110         #if options.freq is None:
111         #    # if no freq was specified, use the mid-point
112         #    r = self.u.freq_range()
113         #    options.freq = float(r[0]+r[1])/2
114             
115         #self.set_gain(options.gain)
116
117         #if options.antenna is not None:
118         #    print "Selecting antenna %s" % (options.antenna,)
119         #    self.subdev.select_rx_antenna(options.antenna)
120
121         if self.show_debug_info:
122         #    self.myform['decim'].set_value(self.u.decim())
123             self.myform['fs@gbe'].set_value(input_rate)
124         #    self.myform['dbname'].set_value("0x%04X" % (self.u.daughterboard_id(),)) # FIXME: add text name
125             self.myform['baseband'].set_value(0)
126             self.myform['ddc'].set_value(0)
127
128         #if not(self.set_freq(options.freq)):
129         #    self._set_status_msg("Failed to set initial frequency")
130
131     def _set_status_msg(self, msg):
132         self.frame.GetStatusBar().SetStatusText(msg, 0)
133
134     def _build_gui(self, vbox):
135
136         def _form_set_freq(kv):
137             return self.set_freq(kv['freq'])
138             
139         vbox.Add(self.scope.win, 10, wx.EXPAND)
140         
141         # add control area at the bottom
142         self.myform = myform = form.form()
143         hbox = wx.BoxSizer(wx.HORIZONTAL)
144         hbox.Add((5,0), 0, 0)
145         myform['freq'] = form.float_field(
146             parent=self.panel, sizer=hbox, label="Center freq", weight=1,
147             callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
148
149         hbox.Add((5,0), 0, 0)
150         #g = self.u.gain_range()
151
152         # some configurations don't have gain control
153         if 0 and g[1] > g[0]:
154             myform['gain'] = form.slider_field(parent=self.panel, sizer=hbox, label="Gain",
155                                            weight=3,
156                                            min=int(g[0]), max=int(g[1]),
157                                            callback=self.set_gain)
158
159         hbox.Add((5,0), 0, 0)
160         vbox.Add(hbox, 0, wx.EXPAND)
161
162         self._build_subpanel(vbox)
163
164     def _build_subpanel(self, vbox_arg):
165         # build a secondary information panel (sometimes hidden)
166
167         # FIXME figure out how to have this be a subpanel that is always
168         # created, but has its visibility controlled by foo.Show(True/False)
169         
170         def _form_set_decim(kv):
171             return self.set_decim(kv['decim'])
172
173         if not(self.show_debug_info):
174             return
175
176         panel = self.panel
177         vbox = vbox_arg
178         myform = self.myform
179
180         #panel = wx.Panel(self.panel, -1)
181         #vbox = wx.BoxSizer(wx.VERTICAL)
182
183         hbox = wx.BoxSizer(wx.HORIZONTAL)
184         hbox.Add((5,0), 0)
185
186         myform['decim'] = form.int_field(
187             parent=panel, sizer=hbox, label="Decim",
188             callback=myform.check_input_and_call(_form_set_decim, self._set_status_msg))
189
190         hbox.Add((5,0), 1)
191         myform['fs@gbe'] = form.static_float_field(
192             parent=panel, sizer=hbox, label="Fs@GbE")
193
194         hbox.Add((5,0), 1)
195         myform['dbname'] = form.static_text_field(
196             parent=panel, sizer=hbox)
197
198         hbox.Add((5,0), 1)
199         myform['baseband'] = form.static_float_field(
200             parent=panel, sizer=hbox, label="Analog BB")
201
202         hbox.Add((5,0), 1)
203         myform['ddc'] = form.static_float_field(
204             parent=panel, sizer=hbox, label="DDC")
205
206         hbox.Add((5,0), 0)
207         vbox.Add(hbox, 0, wx.EXPAND)
208         ##### db control stuff #####
209         self.subscribe('cal_div_lo_freq', lambda x: self.u.set_lo_freq(x) and time.sleep(0.01))
210         self.subscribe('cal_div_lo_freq', self.u.set_center_freq) #TODO should be combined with set lo freq
211         self.subscribe('cal_div_cal_freq', lambda x: self.u.set_cal_freq(x) and time.sleep(0.01))
212         self.subscribe('db_ctrl_atten0', self.u.set_attenuation0)
213         self.subscribe('db_ctrl_atten1', self.u.set_attenuation1)
214         self.subscribe('sys_beaming', self.u.set_beamforming)
215         #self.subscribe('db_ctrl_10db', self.u.set_10dB_atten)
216         self.subscribe('db_ctrl_adcgain', self.u.set_adc_gain)
217         self.subscribe('db_ctrl_diggain', self.u.set_digital_gain)
218         self.subscribe('db_ctrl_dcoffset', self.u.set_dc_offset_comp)
219         self.subscribe('db_ctrl_bandsel', self.u.set_band_select)
220         self.subscribe('db_ctrl_type', self.u.select_rx_antenna)
221         self.subscribe('db_test_signal', self.u.set_test_signal)
222         self['db_ctrl_bandsel'] = 'A'
223         self['cal_div_lo_freq'] = 2.1e9
224         self['cal_div_cal_freq'] = 2.102e9
225         self['db_ctrl_atten0'] = 0
226         self['db_ctrl_atten1'] = 0
227         #self['db_ctrl_10db'] = False
228         self['db_ctrl_adcgain'] = False
229         self['db_ctrl_dcoffset'] = False
230         self['db_ctrl_diggain'] = 0.0
231         self['db_ctrl_type'] = 'rf'
232         self['db_test_signal'] = vrt.VRT_TEST_SIG_NORMAL
233         self['sys_beaming'] = [16.7e6, 0, 0, 0]
234         #slider and box for freqs
235         for key, name in (('cal_div_lo_freq', 'LO Freq'), ('cal_div_cal_freq', 'Cal Freq')):
236             hbox = wx.BoxSizer(wx.HORIZONTAL)
237             hbox.AddSpacer(10)
238             forms.text_box(
239                 label=name,
240                 ps=self,
241                 key=key,
242                 sizer=hbox,
243                 parent=panel,
244                 proportion=0,
245                 converter=forms.float_converter()
246             )
247             hbox.AddSpacer(20)
248             forms.slider(
249                 ps=self,
250                 key=key,
251                 minimum=0,   #TODO get bounds from cal_div, from vrt...
252                 maximum=int(3.5e9),
253                 step_size=int(5e6),
254                 cast=float,
255                 sizer=hbox,
256                 parent=panel,
257                 proportion=2,
258             )
259             hbox.AddSpacer(10)
260             vbox.Add(hbox, 0, wx.EXPAND)
261         ############################################
262         hbox = wx.BoxSizer(wx.HORIZONTAL)
263         hbox.AddSpacer(10)
264         #create slider for atten
265         atten0_txt_box = forms.static_text(
266             label='Attenuation (0)',
267             ps=self,
268             key='db_ctrl_atten0',
269             sizer=hbox,
270             parent=panel,
271             proportion=0,
272             converter=forms.int_converter()
273         )
274         hbox.AddSpacer(20)
275         atten0_slider = forms.slider(
276             ps=self,
277             key='db_ctrl_atten0',
278             minimum=0,
279             maximum=31,
280             step_size=1,
281             cast=int,
282             sizer=hbox,
283             parent=panel,
284             proportion=2,
285         )
286         hbox.AddSpacer(10)
287         #create slider for atten
288         forms.static_text(
289             label='Attenuation (1)',
290             ps=self,
291             key='db_ctrl_atten1',
292             sizer=hbox,
293             parent=panel,
294             proportion=0,
295             converter=forms.int_converter()
296         )
297         hbox.AddSpacer(20)
298         forms.slider(
299             ps=self,
300             key='db_ctrl_atten1',
301             minimum=0,
302             maximum=31,
303             step_size=1,
304             cast=int,
305             sizer=hbox,
306             parent=panel,
307             proportion=2,
308         )
309         hbox.AddSpacer(10)
310         def update_atten0(*args):
311             for form_obj in (atten0_txt_box, atten0_slider): form_obj.Enable(self['db_ctrl_bandsel'] > 'B')
312         update_atten0()
313         self.subscribe('db_ctrl_bandsel', update_atten0)
314         #create checkbox for 10dB att
315         #forms.check_box(
316         #    label='10dB Attenuation',
317         #    ps=self,
318         #    key='db_ctrl_10db',
319         #    sizer=hbox,
320         #    parent=panel,
321         #    proportion=1,
322         #)
323         #hbox.AddSpacer(10)
324         vbox.Add(hbox, 0, wx.EXPAND)
325         hbox2 = wx.BoxSizer(wx.HORIZONTAL)
326         hbox2.AddSpacer(10)
327         forms.static_text(
328             label='ADC Controls',
329             ps=self,
330             key='db_ctrl_diggain',
331             sizer=hbox2,
332             parent=panel,
333             proportion=0,
334             converter=forms.float_converter()
335         )
336         hbox2.AddSpacer(20)
337         #create checkbox for ADC digital gain
338         forms.slider(
339             #label='ADC Digital Gain',
340             ps=self,
341             minimum=0,
342             maximum=6,
343             step_size=0.5,
344             key='db_ctrl_diggain',
345             sizer=hbox2,
346             parent=panel,
347             proportion=2,
348         )
349         hbox2.AddSpacer(10)
350         #create checkbox for 3.5dB ADC gain
351         forms.check_box(
352             label='3.5dB ADC Gain',
353             ps=self,
354             key='db_ctrl_adcgain',
355             sizer=hbox2,
356             parent=panel,
357             proportion=1,
358         )
359         hbox2.AddSpacer(10)
360         #create checkbox for DC Offset Correction in ADC
361         forms.check_box(
362             label='DC Offset Correction',
363             ps=self,
364             key='db_ctrl_dcoffset',
365             sizer=hbox2,
366             parent=panel,
367             proportion=2,
368         )
369         hbox2.AddSpacer(10)
370         vbox.Add(hbox2, 0, wx.EXPAND)
371         hbox = wx.BoxSizer(wx.HORIZONTAL)
372         hbox.AddSpacer(10)
373         #create radio buttons for band sel
374         forms.radio_buttons(
375             label='Band Select',
376             ps=self,
377             key='db_ctrl_bandsel',
378             choices=['A', 'B', 'C', 'D'],
379             labels=['A', 'B', 'C', 'D'],
380             sizer=hbox,
381             parent=panel,
382             proportion=0,
383         )
384         hbox.AddSpacer(10)
385         forms.radio_buttons(
386             label='RF Input',
387             ps=self,
388             key='db_ctrl_type',
389             choices=['rf', 'cal'],
390             labels=['Main RF', 'Calibrator'],
391             sizer=hbox,
392             parent=panel,
393             proportion=0,
394         )
395         hbox.AddSpacer(10)
396         #create radio buttons for band sel
397         types = sorted(
398             filter(lambda x: x.startswith('VRT_TEST_SIG_'), dir(vrt)),
399             lambda x, y: cmp(getattr(vrt, x), getattr(vrt, y)),
400         )
401         forms.drop_down(
402             label='Test Signal',
403             ps=self,
404             key='db_test_signal',
405             choices=map(lambda a: getattr(vrt, a), types),
406             labels=types,
407             sizer=hbox,
408             parent=panel,
409             proportion=0,
410         )
411         hbox.AddSpacer(10)
412         #create radio buttons for type
413         forms.drop_down(
414             label='Beamformer',
415             ps=self,
416             key='sys_beaming',
417             choices=[[16.7e6, 0, 0, 0], [0, 16.7e6, 0, 0], [0, 0, 16.7e6, 0], [0, 0, 0, 16.7e6], [4.19e6]*4],
418             labels=['Ant0', 'Ant1', 'Ant2', 'Ant3', 'Equal Gain'],
419             sizer=hbox,
420             parent=panel,
421             proportion=0,
422         )
423         hbox.AddSpacer(10)
424         vbox.Add(hbox, 0, wx.EXPAND)
425             
426     def set_freq(self, target_freq):
427         """
428         Set the center frequency we're interested in.
429
430         @param target_freq: frequency in Hz
431         @rypte: bool
432
433         Tuning is a two step process.  First we ask the front-end to
434         tune as close to the desired frequency as it can.  Then we use
435         the result of that operation and our target_frequency to
436         determine the value for the digital down converter.
437         """
438         return True
439     
440         r = self.u.set_center_freq(target_freq)
441         
442         if r:
443             self.myform['freq'].set_value(target_freq)     # update displayed value
444             if self.show_debug_info:
445                 self.myform['baseband'].set_value(r.baseband_freq)
446                 self.myform['ddc'].set_value(r.dxc_freq)
447             if not self.options.oscilloscope:
448                 self.scope.win.set_baseband_freq(target_freq)
449             return True
450
451         return False
452
453     def set_gain(self, gain):
454         return True
455     
456         if self.myform.has_key('gain'):
457             self.myform['gain'].set_value(gain)     # update displayed value
458         self.u.set_gain(gain)
459
460     def set_decim(self, decim):
461         return True
462     
463         ok = self.u.set_decim(decim)
464         if not ok:
465             print "set_decim failed"
466         #input_rate = self.u.adc_rate() / self.u.decim()
467         input_rate = 120e6/4
468         self.scope.set_sample_rate(input_rate)
469         if self.show_debug_info:  # update displayed values
470             self.myform['decim'].set_value(self.u.decim())
471             self.myform['fs@gbe'].set_value(input_rate)
472         return ok
473
474     def _setup_events(self):
475         if not self.options.waterfall and not self.options.oscilloscope:
476             self.scope.win.Bind(wx.EVT_LEFT_DCLICK, self.evt_left_dclick)
477             
478     def evt_left_dclick(self, event):
479         (ux, uy) = self.scope.win.GetXY(event)
480         if event.CmdDown():
481             # Re-center on maximum power
482             points = self.scope.win._points
483             if self.scope.win.peak_hold:
484                 if self.scope.win.peak_vals is not None:
485                     ind = numpy.argmax(self.scope.win.peak_vals)
486                 else:
487                     ind = int(points.shape()[0]/2)
488             else:
489                 ind = numpy.argmax(points[:,1])
490             (freq, pwr) = points[ind]
491             target_freq = freq/self.scope.win._scale_factor
492             print ind, freq, pwr
493             self.set_freq(target_freq)            
494         else:
495             # Re-center on clicked frequency
496             target_freq = ux/self.scope.win._scale_factor
497             self.set_freq(target_freq)
498             
499         
500 def main ():
501     app = stdgui2.stdapp(app_top_block, "QuadRadio FFT", nstatus=1)
502     app.MainLoop()
503
504 if __name__ == '__main__':
505     main ()