Included support for decim=4, by loading non-default firmware.
[debian/gnuradio] / gr-radio-astronomy / src / python / usrp_ra_receiver.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2004,2005,2007 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
24 from gnuradio import usrp
25 from usrpm import usrp_dbid
26 from gnuradio import eng_notation
27 from gnuradio.eng_option import eng_option
28 from gnuradio.wxgui import stdgui2, ra_fftsink, ra_stripchartsink, ra_waterfallsink, form, slider
29 from optparse import OptionParser
30 import wx
31 import sys
32 import Numeric 
33 import time
34 import numpy.fft
35 import ephem
36
37 class app_flow_graph(stdgui2.std_top_block):
38         def __init__(self, frame, panel, vbox, argv):
39                 stdgui2.std_top_block.__init__(self, frame, panel, vbox, argv)
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("-z", "--divbase", type="eng_float", default=0.025, help="Y Division increment base") 
67                 parser.add_option("-v", "--stripsize", type="eng_float", default=2400, help="Size of stripchart, in 2Hz samples") 
68                 parser.add_option("-F", "--fft_size", type="eng_float", default=1024, help="Size of FFT")
69                 parser.add_option("-N", "--decln", type="eng_float", default=999.99, help="Observing declination")
70                 parser.add_option("-X", "--prefix", default="./")
71                 parser.add_option("-M", "--fft_rate", type="eng_float", default=8.0, help="FFT Rate")
72                 parser.add_option("-A", "--calib_coeff", type="eng_float", default=1.0, help="Calibration coefficient")
73                 parser.add_option("-B", "--calib_offset", type="eng_float", default=0.0, help="Calibration coefficient")
74                 parser.add_option("-W", "--waterfall", action="store_true", default=False, help="Use Waterfall FFT display")
75                 parser.add_option("-S", "--setimode", action="store_true", default=False, help="Enable SETI processing of spectral data")
76                 parser.add_option("-K", "--setik", type="eng_float", default=1.5, help="K value for SETI analysis")
77                 parser.add_option("-T", "--setibandwidth", type="eng_float", default=12500, help="Instantaneous SETI observing bandwidth--must be divisor of 250Khz")
78                 parser.add_option("-Q", "--seti_range", type="eng_float", default=1.0e6, help="Total scan width, in Hz for SETI scans")
79                 parser.add_option("-Z", "--dual_mode", action="store_true",
80                         default=False, help="Dual-polarization mode")
81                 parser.add_option("-I", "--interferometer", action="store_true", default=False, help="Interferometer mode")
82                 parser.add_option("-D", "--switch_mode", action="store_true", default=False, help="Dicke Switching mode")
83                 parser.add_option("-P", "--reference_divisor", type="eng_float", default=1.0, help="Reference Divisor")
84                 parser.add_option("-U", "--ref_fifo", default="@@@@")
85                 parser.add_option("-k", "--notch_taps", type="int", default=64, help="Number of notch taps")
86                 parser.add_option("-n", "--notches", action="store_true", 
87                     default=False, help="Notch frequencies after all other args")
88                 (options, args) = parser.parse_args()
89
90                 self.setimode = options.setimode
91                 self.dual_mode = options.dual_mode
92                 self.interferometer = options.interferometer
93                 self.normal_mode = False
94                 self.switch_mode = options.switch_mode
95                 self.switch_state = 0
96                 self.reference_divisor = options.reference_divisor
97                 self.ref_fifo = options.ref_fifo
98                 
99                 self.NOTCH_TAPS = options.notch_taps
100                 self.notches = Numeric.zeros(self.NOTCH_TAPS,Numeric.Float64)
101                 # Get notch locations
102                 j = 0
103                 for i in args:
104                         self.notches[j] = float(i)
105                         j = j + 1
106                 
107                 self.use_notches = options.notches
108                 
109                 if (self.ref_fifo != "@@@@"):
110                         self.ref_fifo_file = open (self.ref_fifo, "w")
111                 
112                 modecount = 0
113                 for modes in (self.dual_mode, self.interferometer):
114                         if (modes == True):
115                                 modecount = modecount + 1
116                                 
117                 if (modecount > 1):
118                         print "must select only 1 of --dual_mode, or --interferometer"
119                         sys.exit(1)
120                         
121                 self.chartneeded = True
122                 
123                 if (self.setimode == True):
124                         self.chartneeded = False
125                         
126                 if (self.setimode == True and self.interferometer == True):
127                         print "can't pick both --setimode and --interferometer"
128                         sys.exit(1)
129                         
130                 if (self.setimode == True and self.switch_mode == True):
131                         print "can't pick both --setimode and --switch_mode"
132                         sys.exit(1)
133                 
134                 if (self.interferometer == True and self.switch_mode == True):
135                         print "can't pick both --interferometer and --switch_mode"
136                         sys.exit(1)
137                 
138                 if (modecount == 0):
139                         self.normal_mode = True
140
141                 self.show_debug_info = True
142                 
143                 # Pick up waterfall option
144                 self.waterfall = options.waterfall
145
146                 # SETI mode stuff
147                 self.setimode = options.setimode
148                 self.seticounter = 0
149                 self.setik = options.setik
150                 self.seti_fft_bandwidth = int(options.setibandwidth)
151
152                 # Calculate binwidth
153                 binwidth = self.seti_fft_bandwidth / options.fft_size
154
155                 # Use binwidth, and knowledge of likely chirp rates to set reasonable
156                 #  values for SETI analysis code.       We assume that SETI signals will
157                 #  chirp at somewhere between 0.10Hz/sec and 0.25Hz/sec.
158                 #
159                 # upper_limit is the "worst case"--that is, the case for which we have
160                 #  to wait the longest to actually see any drift, due to the quantizing
161                 #  on FFT bins.
162                 upper_limit = binwidth / 0.10
163                 self.setitimer = int(upper_limit * 2.00)
164                 self.scanning = True
165
166                 # Calculate the CHIRP values based on Hz/sec
167                 self.CHIRP_LOWER = 0.10 * self.setitimer
168                 self.CHIRP_UPPER = 0.25 * self.setitimer
169
170                 # Reset hit counters to 0
171                 self.hitcounter = 0
172                 self.s1hitcounter = 0
173                 self.s2hitcounter = 0
174                 self.avgdelta = 0
175                 # We scan through 2Mhz of bandwidth around the chosen center freq
176                 self.seti_freq_range = options.seti_range
177                 # Calculate lower edge
178                 self.setifreq_lower = options.freq - (self.seti_freq_range/2)
179                 self.setifreq_current = options.freq
180                 # Calculate upper edge
181                 self.setifreq_upper = options.freq + (self.seti_freq_range/2)
182
183                 # Maximum "hits" in a line
184                 self.nhits = 20
185
186                 # Number of lines for analysis
187                 self.nhitlines = 4
188
189                 # We change center frequencies based on nhitlines and setitimer
190                 self.setifreq_timer = self.setitimer * (self.nhitlines * 5)
191
192                 # Create actual timer
193                 self.seti_then = time.time()
194
195                 # The hits recording array
196                 self.hits_array = Numeric.zeros((self.nhits,self.nhitlines), Numeric.Float64)
197                 self.hit_intensities = Numeric.zeros((self.nhits,self.nhitlines), Numeric.Float64)
198                 # Calibration coefficient and offset
199                 self.calib_coeff = options.calib_coeff
200                 self.calib_offset = options.calib_offset
201                 if self.calib_offset < -750:
202                         self.calib_offset = -750
203                 if self.calib_offset > 750:
204                         self.calib_offset = 750
205
206                 if self.calib_coeff < 1:
207                         self.calib_coeff = 1
208                 if self.calib_coeff > 100:
209                         self.calib_coeff = 100
210
211                 self.integ = options.integ
212                 self.avg_alpha = options.avg
213                 self.gain = options.gain
214                 self.decln = options.decln
215
216                 # Set initial values for datalogging timed-output
217                 self.continuum_then = time.time()
218                 self.spectral_then = time.time()
219                 
220           
221                 # build the graph
222
223                 self.subdev = [(0, 0), (0,0)]
224                 
225                 #
226                 # If SETI mode, we always run at maximum USRP decimation
227                 #
228                 if (self.setimode):
229                         options.decim = 256
230                 
231                 if (self.dual_mode == True and self.decim <= 4):
232                         print "Cannot use decim <= 4 with dual_mode"
233                         sys.exit(1)
234
235                 if (self.dual_mode == False and self.interferometer == False):
236                         if (options.decim > 4):
237                                 self.u = usrp.source_c(decim_rate=options.decim,fusb_block_size=8192)
238                         else:
239                                 self.u = usrp.source_c(decim_rate=options.decim,fusb_block_size=8192, fpga_filename="std_4rx_0tx.rbf")
240                         self.u.set_mux(usrp.determine_rx_mux_value(self.u, options.rx_subdev_spec))
241                         # determine the daughterboard subdevice we're using
242                         self.subdev[0] = usrp.selected_subdev(self.u, options.rx_subdev_spec)
243                         self.subdev[1] = self.subdev[0]
244                         self.cardtype = self.subdev[0].dbid()
245                 else:
246                         self.u=usrp.source_c(decim_rate=options.decim, nchan=2,fusb_block_size=8192)
247                         self.subdev[0] = usrp.selected_subdev(self.u, (0, 0))
248                         self.subdev[1] = usrp.selected_subdev(self.u, (1, 0))
249                         self.cardtype = self.subdev[0].dbid()
250                         self.u.set_mux(0x32103210)
251                         c1 = self.subdev[0].name()
252                         c2 = self.subdev[1].name()
253                         if (c1 != c2):
254                                 print "Must have identical cardtypes for --dual_mode or --interferometer"
255                                 sys.exit(1)
256                 #
257                 # Set 8-bit mode
258                 #
259                 width = 8
260                 shift = 8
261                 format = self.u.make_format(width, shift)
262                 r = self.u.set_format(format)
263                 
264                 # Set initial declination
265                 self.decln = options.decln
266
267                 input_rate = self.u.adc_freq() / self.u.decim_rate()
268                 self.bw = input_rate
269                 #
270                 # Set prefix for data files
271                 #
272                 self.prefix = options.prefix
273
274                 #
275                 # The lower this number, the fewer sample frames are dropped
276                 #  in computing the FFT.  A sampled approach is taken to
277                 #  computing the FFT of the incoming data, which reduces
278                 #  sensitivity.  Increasing sensitivity inreases CPU loading.
279                 #
280                 self.fft_rate = options.fft_rate
281
282                 self.fft_size = int(options.fft_size)
283
284                 # This buffer is used to remember the most-recent FFT display
285                 #       values.  Used later by self.write_spectral_data() to write
286                 #       spectral data to datalogging files, and by the SETI analysis
287                 #       function.
288                 #
289                 self.fft_outbuf = Numeric.zeros(self.fft_size, Numeric.Float64)
290
291                 #
292                 # If SETI mode, only look at seti_fft_bandwidth
293                 #       at a time.
294                 #
295                 if (self.setimode):
296                         self.fft_input_rate = self.seti_fft_bandwidth
297
298                         #
299                         # Build a decimating bandpass filter
300                         #
301                         self.fft_input_taps = gr.firdes.complex_band_pass (1.0,
302                            input_rate,
303                            -(int(self.fft_input_rate/2)), int(self.fft_input_rate/2), 200,
304                            gr.firdes.WIN_HAMMING, 0)
305
306                         #
307                         # Compute required decimation factor
308                         #
309                         decimation = int(input_rate/self.fft_input_rate)
310                         self.fft_bandpass = gr.fir_filter_ccc (decimation, 
311                                 self.fft_input_taps)
312                 else:
313                         self.fft_input_rate = input_rate
314
315                 # Set up FFT display
316                 if self.waterfall == False:
317                    self.scope = ra_fftsink.ra_fft_sink_c (panel, 
318                            fft_size=int(self.fft_size), sample_rate=self.fft_input_rate,
319                            fft_rate=int(self.fft_rate), title="Spectral",  
320                            ofunc=self.fft_outfunc, xydfunc=self.xydfunc)
321                 else:
322                         self.scope = ra_waterfallsink.waterfall_sink_c (panel,
323                                 fft_size=int(self.fft_size), sample_rate=self.fft_input_rate,
324                                 fft_rate=int(self.fft_rate), title="Spectral", ofunc=self.fft_outfunc, size=(1100, 600), xydfunc=self.xydfunc, ref_level=0, span=10)
325
326                 # Set up ephemeris data
327                 self.locality = ephem.Observer()
328                 self.locality.long = str(options.longitude)
329                 self.locality.lat = str(options.latitude)
330                 
331                 # We make notes about Sunset/Sunrise in Continuum log files
332                 self.sun = ephem.Sun()
333                 self.sunstate = "??"
334
335                 # Set up stripchart display
336                 tit = "Continuum"
337                 if (self.dual_mode != False):
338                         tit = "H+V Continuum"
339                 if (self.interferometer != False):
340                         tit = "East x West Correlation"
341                 self.stripsize = int(options.stripsize)
342                 if self.chartneeded == True:
343                         self.chart = ra_stripchartsink.stripchart_sink_f (panel,
344                                 stripsize=self.stripsize,
345                                 title=tit,
346                                 xlabel="LMST Offset (Seconds)",
347                                 scaling=1.0, ylabel=options.ylabel,
348                                 divbase=options.divbase)
349
350                 # Set center frequency
351                 self.centerfreq = options.freq
352
353                 # Set observing frequency (might be different from actual programmed
354                 #        RF frequency)
355                 if options.observing == 0.0:
356                         self.observing = options.freq
357                 else:
358                         self.observing = options.observing
359
360                 # Remember our input bandwidth
361                 self.bw = input_rate
362                 
363                 #
364                 # 
365                 # The strip chart is fed at a constant 1Hz rate
366                 #
367
368                 #
369                 # Call constructors for receive chains
370                 #
371                 
372                 if (self.dual_mode == True):
373                         self.setup_dual (self.setimode)
374                         
375                 if (self.interferometer == True):
376                         self.setup_interferometer(self.setimode)
377                                 
378                 if (self.normal_mode == True):
379                         self.setup_normal(self.setimode)
380                         
381                 if (self.setimode == True):
382                         self.setup_seti()
383
384                 self._build_gui(vbox)
385
386                 # Make GUI agree with command-line
387                 self.integ = options.integ
388                 if self.setimode == False:
389                         self.myform['integration'].set_value(int(options.integ))
390                         self.myform['offset'].set_value(self.calib_offset)
391                         self.myform['dcgain'].set_value(self.calib_coeff)
392                 self.myform['average'].set_value(int(options.avg))
393
394
395                 if self.setimode == False:
396                         # Make integrator agree with command line
397                         self.set_integration(int(options.integ))
398
399                 self.avg_alpha = options.avg
400
401                 # Make spectral averager agree with command line
402                 if options.avg != 1.0:
403                         self.scope.set_avg_alpha(float(1.0/options.avg))
404                         self.scope.set_average(True)
405
406                 if self.setimode == False:
407                         # Set division size
408                         self.chart.set_y_per_div(options.division)
409                         # Set reference(MAX) level
410                         self.chart.set_ref_level(options.reflevel)
411
412                 # set initial values
413
414                 if options.gain is None:
415                         # if no gain was specified, use the mid-point in dB
416                         g = self.subdev[0].gain_range()
417                         options.gain = float(g[0]+g[1])/2
418
419                 if options.freq is None:
420                         # if no freq was specified, use the mid-point
421                         r = self.subdev[0].freq_range()
422                         options.freq = float(r[0]+r[1])/2
423
424                 # Set the initial gain control
425                 self.set_gain(options.gain)
426
427                 if not(self.set_freq(options.freq)):
428                         self._set_status_msg("Failed to set initial frequency")
429
430                 # Set declination
431                 self.set_decln (self.decln)
432
433
434                 # RF hardware information
435                 self.myform['decim'].set_value(self.u.decim_rate())
436                 self.myform['USB BW'].set_value(self.u.adc_freq() / self.u.decim_rate())
437                 if (self.dual_mode == True):
438                         self.myform['dbname'].set_value(self.subdev[0].name()+'/'+self.subdev[1].name())
439                 else:
440                         self.myform['dbname'].set_value(self.subdev[0].name())
441
442                 # Set analog baseband filtering, if DBS_RX
443                 if self.cardtype in (usrp_dbid.DBS_RX, usrp_dbid.DBS_RX_REV_2_1):
444                         lbw = (self.u.adc_freq() / self.u.decim_rate()) / 2
445                         if lbw < 1.0e6:
446                                 lbw = 1.0e6
447                         self.subdev[0].set_bw(lbw)
448                         self.subdev[1].set_bw(lbw)
449                         
450                 # Start the timer for the LMST display and datalogging
451                 self.lmst_timer.Start(1000)
452                 if (self.switch_mode == True):
453                         self.other_timer.Start(330)
454
455
456         def _set_status_msg(self, msg):
457                 self.frame.GetStatusBar().SetStatusText(msg, 0)
458
459         def _build_gui(self, vbox):
460
461                 def _form_set_freq(kv):
462                         # Adjust current SETI frequency, and limits
463                         self.setifreq_lower = kv['freq'] - (self.seti_freq_range/2)
464                         self.setifreq_current = kv['freq']
465                         self.setifreq_upper = kv['freq'] + (self.seti_freq_range/2)
466
467                         # Reset SETI analysis timer
468                         self.seti_then = time.time()
469                         # Zero-out hits array when changing frequency
470                         self.hits_array[:,:] = 0.0
471                         self.hit_intensities[:,:] = -60.0
472
473                         return self.set_freq(kv['freq'])
474
475                 def _form_set_decln(kv):
476                         return self.set_decln(kv['decln'])
477
478                 # Position the FFT display
479                 vbox.Add(self.scope.win, 15, wx.EXPAND)
480
481                 if self.setimode == False:
482                         # Position the Total-power stripchart
483                         vbox.Add(self.chart.win, 15, wx.EXPAND)
484                 
485                 # add control area at the bottom
486                 self.myform = myform = form.form()
487                 hbox = wx.BoxSizer(wx.HORIZONTAL)
488                 hbox.Add((7,0), 0, wx.EXPAND)
489                 vbox1 = wx.BoxSizer(wx.VERTICAL)
490                 myform['freq'] = form.float_field(
491                         parent=self.panel, sizer=vbox1, label="Center freq", weight=1,
492                         callback=myform.check_input_and_call(_form_set_freq, self._set_status_msg))
493
494                 vbox1.Add((4,0), 0, 0)
495
496                 myform['lmst_high'] = form.static_text_field(
497                         parent=self.panel, sizer=vbox1, label="Current LMST", weight=1)
498                 vbox1.Add((4,0), 0, 0)
499
500                 if self.setimode == False:
501                         myform['spec_data'] = form.static_text_field(
502                                 parent=self.panel, sizer=vbox1, label="Spectral Cursor", weight=1)
503                         vbox1.Add((4,0), 0, 0)
504
505                 vbox2 = wx.BoxSizer(wx.VERTICAL)
506                 if self.setimode == False:
507                         vbox3 = wx.BoxSizer(wx.VERTICAL)
508                 g = self.subdev[0].gain_range()
509                 myform['gain'] = form.slider_field(parent=self.panel, sizer=vbox2, label="RF Gain",
510                                                                                    weight=1,
511                                                                                    min=int(g[0]), max=int(g[1]),
512                                                                                    callback=self.set_gain)
513
514                 vbox2.Add((4,0), 0, 0)
515                 if self.setimode == True:
516                         max_savg = 100
517                 else:
518                         max_savg = 3000
519                 myform['average'] = form.slider_field(parent=self.panel, sizer=vbox2, 
520                                         label="Spectral Averaging (FFT frames)", weight=1, min=1, max=max_savg, callback=self.set_averaging)
521
522                 # Set up scan control button when in SETI mode
523                 if (self.setimode == True):
524                         # SETI scanning control
525                         buttonbox = wx.BoxSizer(wx.HORIZONTAL)
526                         self.scan_control = form.button_with_callback(self.panel,
527                                   label="Scan: On ",
528                                   callback=self.toggle_scanning)
529         
530                         buttonbox.Add(self.scan_control, 0, wx.CENTER)
531                         vbox2.Add(buttonbox, 0, wx.CENTER)
532
533                 vbox2.Add((4,0), 0, 0)
534
535                 if self.setimode == False:
536                         myform['integration'] = form.slider_field(parent=self.panel, sizer=vbox2,
537                                    label="Continuum Integration Time (sec)", weight=1, min=1, max=180, callback=self.set_integration)
538
539                         vbox2.Add((4,0), 0, 0)
540
541                 myform['decln'] = form.float_field(
542                         parent=self.panel, sizer=vbox2, label="Current Declination", weight=1,
543                         callback=myform.check_input_and_call(_form_set_decln))
544                 vbox2.Add((4,0), 0, 0)
545
546                 if self.setimode == False:
547                         myform['offset'] = form.slider_field(parent=self.panel, sizer=vbox3,
548                                 label="Post-Detector Offset", weight=1, min=-750, max=750, 
549                                 callback=self.set_pd_offset)
550                         vbox3.Add((2,0), 0, 0)
551                         myform['dcgain'] = form.slider_field(parent=self.panel, sizer=vbox3,
552                                 label="Post-Detector Gain", weight=1, min=1, max=100, 
553                                 callback=self.set_pd_gain)
554                         vbox3.Add((2,0), 0, 0)
555                 hbox.Add(vbox1, 0, 0)
556                 hbox.Add(vbox2, wx.ALIGN_RIGHT, 0)
557
558                 if self.setimode == False:
559                         hbox.Add(vbox3, wx.ALIGN_RIGHT, 0)
560
561                 vbox.Add(hbox, 0, wx.EXPAND)
562
563                 self._build_subpanel(vbox)
564
565                 self.lmst_timer = wx.PyTimer(self.lmst_timeout)
566                 self.other_timer = wx.PyTimer(self.other_timeout)
567
568
569         def _build_subpanel(self, vbox_arg):
570                 # build a secondary information panel (sometimes hidden)
571
572                 # FIXME figure out how to have this be a subpanel that is always
573                 # created, but has its visibility controlled by foo.Show(True/False)
574                 
575                 if not(self.show_debug_info):
576                         return
577
578                 panel = self.panel
579                 vbox = vbox_arg
580                 myform = self.myform
581
582                 #panel = wx.Panel(self.panel, -1)
583                 #vbox = wx.BoxSizer(wx.VERTICAL)
584
585                 hbox = wx.BoxSizer(wx.HORIZONTAL)
586                 hbox.Add((5,0), 0)
587                 myform['decim'] = form.static_float_field(
588                         parent=panel, sizer=hbox, label="Decim")
589
590                 hbox.Add((5,0), 1)
591                 myform['USB BW'] = form.static_float_field(
592                         parent=panel, sizer=hbox, label="USB BW")
593
594                 hbox.Add((5,0), 1)
595                 myform['dbname'] = form.static_text_field(
596                         parent=panel, sizer=hbox)
597
598                 hbox.Add((5,0), 1)
599                 myform['baseband'] = form.static_float_field(
600                         parent=panel, sizer=hbox, label="Analog BB")
601
602                 hbox.Add((5,0), 1)
603                 myform['ddc'] = form.static_float_field(
604                         parent=panel, sizer=hbox, label="DDC")
605
606                 hbox.Add((5,0), 0)
607                 vbox.Add(hbox, 0, wx.EXPAND)
608
609                 
610                 
611         def set_freq(self, target_freq):
612                 """
613                 Set the center frequency we're interested in.
614
615                 @param target_freq: frequency in Hz
616                 @rypte: bool
617
618                 Tuning is a two step process.  First we ask the front-end to
619                 tune as close to the desired frequency as it can.  Then we use
620                 the result of that operation and our target_frequency to
621                 determine the value for the digital down converter.
622                 """
623                 #
624                 # Everything except BASIC_RX should support usrp.tune()
625                 #
626                 if not (self.cardtype == usrp_dbid.BASIC_RX):
627                         r = usrp.tune(self.u, self.subdev[0].which(), self.subdev[0], target_freq)
628                         r = usrp.tune(self.u, self.subdev[1].which(), self.subdev[1], target_freq)
629                 else:
630                         r = self.u.set_rx_freq(0, target_freq)
631                         f = self.u.rx_freq(0)
632                         if abs(f-target_freq) > 2.0e3:
633                                 r = 0
634                 if r:
635                         self.myform['freq'].set_value(target_freq)         # update displayed value
636                         #
637                         # Make sure calibrator knows our target freq
638                         #
639
640                         # Remember centerfreq---used for doppler calcs
641                         delta = self.centerfreq - target_freq
642                         self.centerfreq = target_freq
643                         self.observing -= delta
644                         self.scope.set_baseband_freq (self.observing)
645
646                         self.myform['baseband'].set_value(r.baseband_freq)
647                         self.myform['ddc'].set_value(r.dxc_freq)
648                         
649                         if (self.use_notches):
650                                 self.compute_notch_taps(self.notches)
651                                 if self.dual_mode == False and self.interferometer == False:
652                                         self.notch_filt.set_taps(self.notch_taps)
653                                 else:
654                                         self.notch_filt1.set_taps(self.notch_taps)
655                                         self.notch_filt2.set_taps(self.notch_taps)
656
657                         return True
658
659                 return False
660
661         def set_decln(self, dec):
662                 self.decln = dec
663                 self.myform['decln'].set_value(dec)             # update displayed value
664
665         def set_gain(self, gain):
666                 self.myform['gain'].set_value(gain)             # update displayed value
667                 self.subdev[0].set_gain(gain)
668                 self.subdev[1].set_gain(gain)
669                 self.gain = gain
670
671         def set_averaging(self, avval):
672                 self.myform['average'].set_value(avval)
673                 self.scope.set_avg_alpha(1.0/(avval))
674                 self.scope.set_average(True)
675                 self.avg_alpha = avval
676
677         def set_integration(self, integval):
678                 if self.setimode == False:
679                         self.integrator.set_taps(1.0/((integval)*(self.bw/2)))
680                 self.myform['integration'].set_value(integval)
681                 self.integ = integval
682
683         #
684         # Timeout function
685         # Used to update LMST display, as well as current
686         #  continuum value
687         #
688         # We also write external data-logging files here
689         #
690         def lmst_timeout(self):
691                 self.locality.date = ephem.now()
692                 if self.setimode == False:
693                  x = self.probe.level()
694                 sidtime = self.locality.sidereal_time()
695                 # LMST
696                 s = str(ephem.hours(sidtime)) + " " + self.sunstate
697                 # Continuum detector value
698                 if self.setimode == False:
699                  sx = "%7.4f" % x
700                  s = s + "\nDet: " + str(sx)
701                 else:
702                  sx = "%2d" % self.hitcounter
703                  s1 = "%2d" % self.s1hitcounter
704                  s2 = "%2d" % self.s2hitcounter
705                  sa = "%4.2f" % self.avgdelta
706                  sy = "%3.1f-%3.1f" % (self.CHIRP_LOWER, self.CHIRP_UPPER)
707                  s = s + "\nHits: " + str(sx) + "\nS1:" + str(s1) + " S2:" + str(s2)
708                  s = s + "\nAv D: " + str(sa) + "\nCh lim: " + str(sy)
709
710                 self.myform['lmst_high'].set_value(s)
711
712                 #
713                 # Write data out to recording files
714                 #
715                 if self.setimode == False:
716                  self.write_continuum_data(x,sidtime)
717                  self.write_spectral_data(self.fft_outbuf,sidtime)
718
719                 else:
720                  self.seti_analysis(self.fft_outbuf,sidtime)
721                  now = time.time()
722                  if ((self.scanning == True) and ((now - self.seti_then) > self.setifreq_timer)):
723                          self.seti_then = now
724                          self.setifreq_current = self.setifreq_current + self.fft_input_rate
725                          if (self.setifreq_current > self.setifreq_upper):
726                                  self.setifreq_current = self.setifreq_lower
727                          self.set_freq(self.setifreq_current)
728                          # Make sure we zero-out the hits array when changing
729                          #       frequency.
730                          self.hits_array[:,:] = 0.0
731                          self.hit_intensities[:,:] = 0.0
732         
733         def other_timeout(self):
734                 if (self.switch_state == 0):
735                         self.switch_state = 1
736                         
737                 elif (self.switch_state == 1):
738                         self.switch_state = 0
739                         
740                 if (self.switch_state == 0):
741                         self.mute.set_n(1)
742                         self.cmute.set_n(int(1.0e9))
743                         
744                 elif (self.switch_state == 1):
745                         self.mute.set_n(int(1.0e9))
746                         self.cmute.set_n(1)
747                         
748                 if (self.ref_fifo != "@@@@"):
749                         self.ref_fifo_file.write(str(self.switch_state)+"\n")
750                         self.ref_fifo_file.flush()
751
752                 self.avg_reference_value = self.cprobe.level()
753                         
754                 #
755                 # Set reference value
756                 #
757                 self.reference_level.set_k(-1.0 * (self.avg_reference_value/self.reference_divisor))
758
759         def fft_outfunc(self,data,l):
760                 self.fft_outbuf=data
761
762         def write_continuum_data(self,data,sidtime):
763         
764                 # Create localtime structure for producing filename
765                 foo = time.localtime()
766                 pfx = self.prefix
767                 filenamestr = "%s/%04d%02d%02d%02d" % (pfx, foo.tm_year, 
768                    foo.tm_mon, foo.tm_mday, foo.tm_hour)
769         
770                 # Open the data file, appending
771                 continuum_file = open (filenamestr+".tpdat","a")
772           
773                 flt = "%6.3f" % data
774                 inter = self.decln
775                 integ = self.integ
776                 fc = self.observing
777                 fc = fc / 1000000
778                 bw = self.bw
779                 bw = bw / 1000000
780                 ga = self.gain
781         
782                 now = time.time()
783         
784                 #
785                 # If time to write full header info (saves storage this way)
786                 #
787                 if (now - self.continuum_then > 20):
788                         self.sun.compute(self.locality)
789                         enow = ephem.now()
790                         sunset = self.locality.next_setting(self.sun)
791                         sunrise = self.locality.next_rising(self.sun)
792                         sun_insky = "Down"
793                         self.sunstate = "Dn"
794                         if ((sunrise < enow) and (enow < sunset)):
795                            sun_insky = "Up"
796                            self.sunstate = "Up"
797                         self.continuum_then = now
798                 
799                         continuum_file.write(str(ephem.hours(sidtime))+" "+flt+" Dn="+str(inter)+",")
800                         continuum_file.write("Ti="+str(integ)+",Fc="+str(fc)+",Bw="+str(bw))
801                         continuum_file.write(",Ga="+str(ga)+",Sun="+str(sun_insky)+"\n")
802                 else:
803                         continuum_file.write(str(ephem.hours(sidtime))+" "+flt+"\n")
804         
805                 continuum_file.close()
806                 return(data)
807
808         def write_spectral_data(self,data,sidtime):
809         
810                 now = time.time()
811                 delta = 10
812                         
813                 # If time to write out spectral data
814                 # We don't write this out every time, in order to
815                 #       save disk space.  Since the spectral data are
816                 #       typically heavily averaged, writing this data
817                 #       "once in a while" is OK.
818                 #
819                 if (now - self.spectral_then >= delta):
820                         self.spectral_then = now
821
822                         # Get localtime structure to make filename from
823                         foo = time.localtime()
824                 
825                         pfx = self.prefix
826                         filenamestr = "%s/%04d%02d%02d%02d" % (pfx, foo.tm_year, 
827                            foo.tm_mon, foo.tm_mday, foo.tm_hour)
828         
829                         # Open the file
830                         spectral_file = open (filenamestr+".sdat","a")
831           
832                         # Setup data fields to be written
833                         r = data
834                         inter = self.decln
835                         fc = self.observing
836                         fc = fc / 1000000
837                         bw = self.bw
838                         bw = bw / 1000000
839                         av = self.avg_alpha
840
841                         # Write those fields
842                         spectral_file.write("data:"+str(ephem.hours(sidtime))+" Dn="+str(inter)+",Fc="+str(fc)+",Bw="+str(bw)+",Av="+str(av))
843                         spectral_file.write (" [ ")
844                         for r in data:
845                                 spectral_file.write(" "+str(r))
846
847                         spectral_file.write(" ]\n")
848                         spectral_file.close()
849                         return(data)
850         
851                 return(data)
852
853         def seti_analysis(self,fftbuf,sidtime):
854                 l = len(fftbuf)
855                 x = 0
856                 hits = []
857                 hit_intensities = []
858                 if self.seticounter < self.setitimer:
859                         self.seticounter = self.seticounter + 1
860                         return
861                 else:
862                         self.seticounter = 0
863
864                 # Run through FFT output buffer, computing standard deviation (Sigma)
865                 avg = 0
866                 # First compute average
867                 for i in range(0,l):
868                         avg = avg + fftbuf[i]
869                 avg = avg / l
870
871                 sigma = 0.0
872                 # Then compute standard deviation (Sigma)
873                 for i in range(0,l):
874                         d = fftbuf[i] - avg
875                         sigma = sigma + (d*d)
876
877                 sigma = Numeric.sqrt(sigma/l)
878
879                 #
880                 # Snarfle through the FFT output buffer again, looking for
881                 #        outlying data points
882
883                 start_f = self.observing - (self.fft_input_rate/2)
884                 current_f = start_f
885                 l = len(fftbuf)
886                 f_incr = self.fft_input_rate / l
887                 hit = -1
888
889                 # -nyquist to DC
890                 for i in range(l/2,l):
891                         #
892                         # If current FFT buffer has an item that exceeds the specified
893                         #  sigma
894                         #
895                         if ((fftbuf[i] - avg) > (self.setik * sigma)):
896                                 hits.append(current_f)
897                                 hit_intensities.append(fftbuf[i])
898                         current_f = current_f + f_incr
899
900                 # DC to nyquist
901                 for i in range(0,l/2):
902                         #
903                         # If current FFT buffer has an item that exceeds the specified
904                         #  sigma
905                         #
906                         if ((fftbuf[i] - avg) > (self.setik * sigma)):
907                                 hits.append(current_f)
908                                 hit_intensities.append(fftbuf[i])
909                         current_f = current_f + f_incr
910
911                 # No hits
912                 if (len(hits) <= 0):
913                         return
914
915
916                 #
917                 # OK, so we have some hits in the FFT buffer
918                 #       They'll have a rather substantial gauntlet to run before
919                 #       being declared a real "hit"
920                 #
921
922                 # Update stats
923                 self.s1hitcounter = self.s1hitcounter + len(hits)
924
925                 # Weed out buffers with an excessive number of
926                 #       signals above Sigma
927                 if (len(hits) > self.nhits):
928                         return
929
930
931                 # Weed out FFT buffers with apparent multiple narrowband signals
932                 #       separated significantly in frequency.  This means that a
933                 #       single signal spanning multiple bins is OK, but a buffer that
934                 #       has multiple, apparently-separate, signals isn't OK.
935                 #
936                 last = hits[0]
937                 ns2 = 1
938                 for i in range(1,len(hits)):
939                         if ((hits[i] - last) > (f_incr*3.0)):
940                                 return
941                         last = hits[i]
942                         ns2 = ns2 + 1
943
944                 self.s2hitcounter = self.s2hitcounter + ns2
945
946                 #
947                 # Run through all available hit buffers, computing difference between
948                 #       frequencies found there, if they're all within the chirp limits
949                 #       declare a good hit
950                 #
951                 good_hit = False
952                 f_ds = Numeric.zeros(self.nhitlines, Numeric.Float64)
953                 avg_delta = 0
954                 k = 0
955                 for i in range(0,min(len(hits),len(self.hits_array[:,0]))):
956                         f_ds[0] = abs(self.hits_array[i,0] - hits[i])
957                         for j in range(1,len(f_ds)):
958                            f_ds[j] = abs(self.hits_array[i,j] - self.hits_array[i,0])
959                            avg_delta = avg_delta + f_ds[j]
960                            k = k + 1
961
962                         if (self.seti_isahit (f_ds)):
963                                 good_hit = True
964                                 self.hitcounter = self.hitcounter + 1
965                                 break
966
967                 if (avg_delta/k < (self.seti_fft_bandwidth/2)):
968                         self.avgdelta = avg_delta / k
969
970                 # Save 'n shuffle hits
971                 #  Old hit buffers percolate through the hit buffers
972                 #  (there are self.nhitlines of these buffers)
973                 #  and then drop off the end
974                 #  A consequence is that while the nhitlines buffers are filling,
975                 #  you can get some absurd values for self.avgdelta, because some
976                 #  of the buffers are full of zeros
977                 for i in range(self.nhitlines,1):
978                         self.hits_array[:,i] = self.hits_array[:,i-1]
979                         self.hit_intensities[:,i] = self.hit_intensities[:,i-1]
980
981                 for i in range(0,len(hits)):
982                         self.hits_array[i,0] = hits[i]
983                         self.hit_intensities[i,0] = hit_intensities[i]
984
985                 # Finally, write the hits/intensities buffer
986                 if (good_hit):
987                         self.write_hits(sidtime)
988
989                 return
990
991         def seti_isahit(self,fdiffs):
992                 truecount = 0
993
994                 for i in range(0,len(fdiffs)):
995                         if (fdiffs[i] >= self.CHIRP_LOWER and fdiffs[i] <= self.CHIRP_UPPER):
996                                 truecount = truecount + 1
997
998                 if truecount == len(fdiffs):
999                         return (True)
1000                 else:
1001                         return (False)
1002
1003         def write_hits(self,sidtime):
1004                 # Create localtime structure for producing filename
1005                 foo = time.localtime()
1006                 pfx = self.prefix
1007                 filenamestr = "%s/%04d%02d%02d%02d" % (pfx, foo.tm_year, 
1008                    foo.tm_mon, foo.tm_mday, foo.tm_hour)
1009         
1010                 # Open the data file, appending
1011                 hits_file = open (filenamestr+".seti","a")
1012
1013                 # Write sidtime first
1014                 hits_file.write(str(ephem.hours(sidtime))+" "+str(self.decln)+" ")
1015
1016                 #
1017                 # Then write the hits/hit intensities buffers with enough
1018                 #       "syntax" to allow parsing by external (not yet written!)
1019                 #       "stuff".
1020                 #
1021                 for i in range(0,self.nhitlines):
1022                         hits_file.write(" ")
1023                         for j in range(0,self.nhits):
1024                                 hits_file.write(str(self.hits_array[j,i])+":")
1025                                 hits_file.write(str(self.hit_intensities[j,i])+",")
1026                 hits_file.write("\n")
1027                 hits_file.close()
1028                 return
1029
1030         def xydfunc(self,func,xyv):
1031                 if self.setimode == True:
1032                         return
1033                 magn = int(Numeric.log10(self.observing))
1034                 if (magn == 6 or magn == 7 or magn == 8):
1035                         magn = 6
1036                 dfreq = xyv[0] * pow(10.0,magn)
1037                 if func == 0:
1038                         ratio = self.observing / dfreq
1039                         vs = 1.0 - ratio
1040                         vs *= 299792.0
1041                         if magn >= 9:
1042                            xhz = "Ghz"
1043                         elif magn >= 6:
1044                            xhz = "Mhz"
1045                         elif magn <= 5:
1046                            xhz =  "Khz"
1047                         s = "%.6f%s\n%.3fdB" % (xyv[0], xhz, xyv[1])
1048                         s2 = "\n%.3fkm/s" % vs
1049                         self.myform['spec_data'].set_value(s+s2)
1050                 else:
1051                         tmpnotches = Numeric.zeros(self.NOTCH_TAPS,Numeric.Float64)
1052                         delfreq = -1
1053                         if self.use_notches == True:
1054                                 for i in range(0,len(self.notches)):
1055                                         if self.notches[i] != 0 and abs(self.notches[i] - dfreq) < ((self.bw/self.NOTCH_TAPS)/2.0):
1056                                                 delfreq = i
1057                                                 break
1058                                 j = 0
1059                                 for i in range(0,len(self.notches)):
1060                                         if (i != delfreq):
1061                                                 tmpnotches[j] = self.notches[i]
1062                                                 j = j + 1
1063                                 if (delfreq == -1):
1064                                         for i in range(0,len(tmpnotches)):
1065                                                 if (int(tmpnotches[i]) == 0):
1066                                                         tmpnotches[i] = dfreq
1067                                                         break
1068                                 self.notches = tmpnotches
1069                                 self.compute_notch_taps(self.notches)
1070                                 if self.dual_mode == False and self.interferometer == False:
1071                                         self.notch_filt.set_taps(self.notch_taps)
1072                                 else:
1073                                         self.notch_filt1.set_taps(self.notch_taps)
1074                                         self.notch_filt2.set_taps(self.notch_taps)
1075
1076         def xydfunc_waterfall(self,pos):
1077                 lower = self.observing - (self.seti_fft_bandwidth / 2)
1078                 upper = self.observing + (self.seti_fft_bandwidth / 2)
1079                 binwidth = self.seti_fft_bandwidth / 1024
1080                 s = "%.6fMHz" % ((lower + (pos.x*binwidth)) / 1.0e6)
1081                 self.myform['spec_data'].set_value(s)
1082
1083         def toggle_cal(self):
1084                 if (self.calstate == True):
1085                   self.calstate = False
1086                   self.u.write_io(0,0,(1<<15))
1087                   self.calibrator.SetLabel("Calibration Source: Off")
1088                 else:
1089                   self.calstate = True
1090                   self.u.write_io(0,(1<<15),(1<<15))
1091                   self.calibrator.SetLabel("Calibration Source: On")
1092
1093         def toggle_annotation(self):
1094                 if (self.annotate_state == True):
1095                   self.annotate_state = False
1096                   self.annotation.SetLabel("Annotation: Off")
1097                 else:
1098                   self.annotate_state = True
1099                   self.annotation.SetLabel("Annotation: On")
1100         #
1101         # Turn scanning on/off
1102         # Called-back by "Recording" button
1103         #
1104         def toggle_scanning(self):
1105                 # Current scanning?      Flip state
1106                 if (self.scanning == True):
1107                   self.scanning = False
1108                   self.scan_control.SetLabel("Scan: Off")
1109                 # Not scanning
1110                 else:
1111                   self.scanning = True
1112                   self.scan_control.SetLabel("Scan: On ")
1113
1114         def set_pd_offset(self,offs):
1115                  self.myform['offset'].set_value(offs)
1116                  self.calib_offset=offs
1117                  x = self.calib_coeff / 100.0
1118                  self.cal_offs.set_k(offs*(x*8000))
1119
1120         def set_pd_gain(self,gain):
1121                  self.myform['dcgain'].set_value(gain)
1122                  self.cal_mult.set_k(gain*0.01)
1123                  self.calib_coeff = gain
1124                  x = gain/100.0
1125                  self.cal_offs.set_k(self.calib_offset*(x*8000))
1126
1127         def compute_notch_taps(self,notchlist):
1128                  tmptaps = Numeric.zeros(self.NOTCH_TAPS,Numeric.Complex64)
1129                  binwidth = self.bw / self.NOTCH_TAPS
1130  
1131                  for i in range(0,self.NOTCH_TAPS):
1132                          tmptaps[i] = complex(1.0,0.0)
1133  
1134                  for i in notchlist:
1135                          diff = i - self.observing
1136                          if int(i) == 0:
1137                                  break
1138                          if ((i < (self.observing - self.bw/2)) or (i > (self.observing + self.bw/2))):
1139                                  continue
1140                          if (diff > 0):
1141                                  idx = diff / binwidth
1142                                  idx = round(idx)
1143                                  idx = int(idx)
1144                                  if (idx < 0 or idx > (self.NOTCH_TAPS/2)):
1145                                          break
1146                                  tmptaps[idx] = complex(0.0, 0.0)
1147
1148                          if (diff < 0):
1149                                  idx = -diff / binwidth
1150                                  idx = round(idx)
1151                                  idx = (self.NOTCH_TAPS/2) - idx
1152                                  idx = int(idx+(self.NOTCH_TAPS/2))
1153                                  if (idx < 0 or idx >= (self.NOTCH_TAPS)):
1154                                          break
1155                                  tmptaps[idx] = complex(0.0, 0.0)
1156
1157                  self.notch_taps = numpy.fft.ifft(tmptaps)
1158         
1159         #
1160         # Setup common pieces of radiometer mode
1161         #
1162         def setup_radiometer_common(self,n):
1163                 # The IIR integration filter for post-detection
1164                 self.integrator = gr.single_pole_iir_filter_ff(1.0)
1165                 self.integrator.set_taps (1.0/self.bw)
1166                 
1167                 if (self.use_notches == True):
1168                         self.compute_notch_taps(self.notches)
1169                         if (n == 2):
1170                                 self.notch_filt1 = gr.fft_filter_ccc(1, self.notch_taps)
1171                                 self.notch_filt2 = gr.fft_filter_ccc(1, self.notch_taps)
1172                         else:
1173                                 self.notch_filt = gr.fft_filter_ccc(1, self.notch_taps)
1174
1175
1176                 # Signal probe
1177                 self.probe = gr.probe_signal_f()
1178
1179                 #
1180                 # Continuum calibration stuff
1181                 #
1182                 x = self.calib_coeff/100.0
1183                 self.cal_mult = gr.multiply_const_ff(self.calib_coeff/100.0)
1184                 self.cal_offs = gr.add_const_ff(self.calib_offset*(x*8000))
1185                 
1186                 #
1187                 # Mega decimator after IIR filter
1188                 #
1189                 if (self.switch_mode == False):
1190                         self.keepn = gr.keep_one_in_n(gr.sizeof_float, self.bw)
1191                 else:
1192                         self.keepn = gr.keep_one_in_n(gr.sizeof_float, int(self.bw/2))
1193                 
1194                 #
1195                 # For the Dicke-switching scheme
1196                 #
1197                 #self.switch = gr.multiply_const_ff(1.0)
1198                 
1199                 #
1200                 if (self.switch_mode == True):
1201                         self.vector = gr.vector_sink_f()
1202                         self.swkeep = gr.keep_one_in_n(gr.sizeof_float, int(self.bw/3))
1203                         self.mute = gr.keep_one_in_n(gr.sizeof_float, 1)
1204                         self.cmute = gr.keep_one_in_n(gr.sizeof_float, int(1.0e9))
1205                         self.cintegrator = gr.single_pole_iir_filter_ff(1.0/(self.bw/2))        
1206                         self.cprobe = gr.probe_signal_f()
1207                 else:
1208                         self.mute = gr.multiply_const_ff(1.0)
1209                         
1210                         
1211                 self.avg_reference_value = 0.0
1212                 self.reference_level = gr.add_const_ff(0.0)
1213                 
1214         #
1215         # Setup ordinary single-channel radiometer mode
1216         #        
1217         def setup_normal(self, setimode):
1218                 
1219                 self.setup_radiometer_common(1)
1220                 
1221                 self.head = self.u
1222                 if (self.use_notches == True):
1223                         self.shead = self.notch_filt
1224                 else:
1225                         self.shead = self.u
1226                 
1227                 if setimode == False:
1228                                 
1229                         self.detector = gr.complex_to_mag_squared()
1230                         self.connect(self.shead, self.scope)
1231
1232                         if (self.use_notches == False):
1233                                 self.connect(self.head, self.detector, self.mute, self.reference_level,
1234                                         self.integrator, self.keepn, self.cal_mult, self.cal_offs, self.chart)
1235                         else:
1236                                 self.connect(self.head, self.notch_filt, self.detector, self.mute, self.reference_level,
1237                                         self.integrator, self.keepn, self.cal_mult, self.cal_offs, self.chart)
1238                                 
1239                         self.connect(self.cal_offs, self.probe)
1240                         
1241                         #
1242                         # Add a side-chain detector chain, with a different integrator, for sampling
1243                         #   The reference channel data
1244                         # This is used to derive the offset value for self.reference_level, used above
1245                         #
1246                         if (self.switch_mode == True):          
1247                                 self.connect(self.detector, self.cmute, self.cintegrator, self.swkeep, self.cprobe)
1248                         
1249                 return
1250         
1251         #
1252         # Setup dual-channel (two antenna, usual orthogonal polarity probes in the same waveguide)
1253         #
1254         def setup_dual(self, setimode):
1255                 
1256                 self.setup_radiometer_common(2)
1257                 
1258                 self.di = gr.deinterleave(gr.sizeof_gr_complex)
1259                 self.addchans = gr.add_cc ()
1260                 self.detector = gr.add_ff ()
1261                 self.h_power = gr.complex_to_mag_squared()
1262                 self.v_power = gr.complex_to_mag_squared()
1263                 self.connect (self.u, self.di)
1264                 
1265                 if (self.use_notches == True):
1266                         self.connect((self.di, 0), self.notch_filt1, (self.addchans, 0))
1267                         self.connect((self.di, 1), self.notch_filt2, (self.addchans, 1))
1268                 else:
1269                         #
1270                         # For spectral, adding the two channels works, assuming no gross
1271                         #       phase or amplitude error
1272                         self.connect ((self.di, 0), (self.addchans, 0))
1273                         self.connect ((self.di, 1), (self.addchans, 1))
1274                 
1275                 #
1276                 # Connect heads of spectral and total-power chains
1277                 #
1278                 if (self.use_notches == False):
1279                         self.head = self.di
1280                 else:
1281                         self.head = (self.notch_filt1, self.notch_filt2)
1282                         
1283                 self.shead = self.addchans
1284                 
1285                 if (setimode == False):
1286                         #
1287                         # For dual-polarization mode, we compute the sum of the
1288                         #       powers on each channel, after they've been detected
1289                         #
1290                         self.detector = gr.add_ff()
1291                         
1292                         #
1293                         # In dual-polarization mode, we compute things a little differently
1294                         # In effect, we have two radiometer chains, terminating in an adder
1295                         #
1296                         if self.use_notches == True:
1297                                 self.connect(self.notch_filt1, self.h_power)
1298                                 self.connect(self.notch_filt2, self.v_power)
1299                         else:
1300                                 self.connect((self.head, 0), self.h_power)
1301                                 self.connect((self.head, 1), self.v_power)
1302                         self.connect(self.h_power, (self.detector, 0))
1303                         self.connect(self.v_power, (self.detector, 1))
1304                         self.connect(self.detector, self.mute, self.reference_level,
1305                                 self.integrator, self.keepn, self.cal_mult, self.cal_offs, self.chart)
1306                         self.connect(self.cal_offs, self.probe)
1307                         self.connect(self.shead, self.scope)
1308                         
1309                         #
1310                         # Add a side-chain detector chain, with a different integrator, for sampling
1311                         #   The reference channel data
1312                         # This is used to derive the offset value for self.reference_level, used above
1313                         #
1314                         if (self.switch_mode == True):
1315                                 self.connect(self.detector, self.cmute, self.cintegrator, self.swkeep, self.cprobe)                     
1316                 return
1317         
1318         #
1319         # Setup correlating interferometer mode
1320         #
1321         def setup_interferometer(self, setimode):
1322                 self.setup_radiometer_common(2)
1323                 
1324                 self.di = gr.deinterleave(gr.sizeof_gr_complex)
1325                 self.connect (self.u, self.di)
1326                 self.corr = gr.multiply_cc()
1327                 self.c2f = gr.complex_to_float()
1328                 
1329                 self.shead = (self.di, 0)
1330                 
1331                 # Channel 0 to multiply port 0
1332                 # Channel 1 to multiply port 1
1333                 if (self.use_notches == False):
1334                         self.connect((self.di, 0), (self.corr, 0))
1335                         self.connect((self.di, 1), (self.corr, 1))
1336                 else:
1337                         self.connect((self.di, 0), self.notch_filt1, (self.corr, 0))
1338                         self.connect((self.di, 1), self.notch_filt2, (self.corr, 0))
1339                 
1340                 #
1341                 # Multiplier (correlator) to complex-to-float, followed by integrator, etc
1342                 #
1343                 self.connect(self.corr, self.c2f, self.integrator, self.keepn, self.cal_mult, self.cal_offs, self.chart)
1344                 
1345                 #
1346                 # FFT scope gets only 1 channel
1347                 #  FIX THIS, by cross-correlating the *outputs* of two different FFTs, then display
1348                 #  Funky!
1349                 #
1350                 self.connect(self.shead, self.scope)
1351                 
1352                 #
1353                 # Output of correlator/integrator chain to probe
1354                 #
1355                 self.connect(self.cal_offs, self.probe)
1356                 
1357                 return
1358         
1359         #
1360         # Setup SETI mode
1361         #
1362         def setup_seti(self):
1363                 self.connect (self.shead, self.fft_bandpass, self.scope)
1364                 return
1365
1366                 
1367
1368 def main ():
1369         app = stdgui2.stdapp(app_flow_graph, "RADIO ASTRONOMY SPECTRAL/CONTINUUM RECEIVER: $Revision$", nstatus=1)
1370         app.MainLoop()
1371
1372 if __name__ == '__main__':
1373         main ()