Imported Upstream version 3.2.2
[debian/gnuradio] / gnuradio-examples / python / apps / hf_explorer / hfx2.py
1 #!/usr/bin/env python
2 # -*- coding: ANSI_X3.4-1968 -*-
3 # generated by wxGlade 0.4 on Tue Mar 14 10:16:06 2006
4 #
5 # Copyright 2006 Free Software Foundation, Inc.
6 #
7 # This file is part of GNU Radio
8 #
9 # GNU Radio is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3, or (at your option)
12 # any later version.
13 #
14 # GNU Radio is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with GNU Radio; see the file COPYING.  If not, write to
21 # the Free Software Foundation, Inc., 51 Franklin Street,
22 # Boston, MA 02110-1301, USA.
23 #
24 #-----------------------------------------------------------------
25 #
26 #                 +-->(fft)
27 #                 |
28 # (src)->(xlate)--+->(audio filter)--+-->(sel_am)-+--------------+
29 #                                    |            |              |
30 #                                    |          (pll)            |
31 #                                    |            |              |
32 #                                    |   (pll_carrier_scale)     |
33 #                                    |            |              |
34 #                                    |   (pll_carrier_filter)    |
35 #                                    |            |              |
36 #                                    |            +--(fft2)      |
37 #                                    |            |              |
38 #                                    |       +--(c2f3)--+        |
39 #                                    |       |          |        |
40 #                                    |   (phaser1)  (phaser2)    |
41 #                                    |       |          |        |
42 #                                    |       +--(f2c)---+        |
43 #                                    |            |              V
44 #                                    V            +---------->(am_det)
45 #                                  (c2f)                         |
46 #                                    |                         (c2f2)
47 #                                    |                           |
48 #                                    +-->(sel_sb)------------>(combine)
49 #                                                                |
50 #                                                                V
51 #                                  +--------------------------(scale)
52 #                                  |                             |
53 #                                  |                             |
54 #                                  |                            +++
55 #                                  V                            | |
56 #                                (agc)<--(offset)<--(intr)<---(sqr1)
57 #                                  |
58 #                                  V
59 #                                (dst)
60 #                                                       
61 #----------------------------------------------------------------------
62 #
63 # Versions 2.2.1 adds loop antenna automatic tuner
64 #
65 # 2.3.1 adds web control, made AM Sync display optional,
66 # added more comments.
67 #
68 # 2.4.1 updates usrp interface to support auto subdev
69
70 # 2.8.1 changed saved file format from 8-byte complex to
71 # 4-byte short for obvious storage space savings.
72
73 # Web server control disabled by default. Do not enable
74 # until directory structure and scripts are in place.
75 WEB_CONTROL = False
76
77 # Controls display of AM Sync Carrier - turn off for smaller
78 # window if not needed
79 AM_SYNC_DISPLAY = False
80
81 import os, wx, sys, math
82 import wx.lib.evtmgr as em
83 from gnuradio.wxgui import powermate, fftsink2
84 from gnuradio import gr, audio, eng_notation, usrp, gru
85 from gnuradio.eng_option import eng_option
86 from optparse import OptionParser
87
88 ID_BUTTON_1 = wx.NewId()        # LSB button
89 ID_BUTTON_2 = wx.NewId()        # USB
90 ID_BUTTON_3 = wx.NewId()        # AM
91 ID_BUTTON_4 = wx.NewId()        # CW
92 ID_BUTTON_5 = wx.NewId()        # Powermate controls: Upper audio freq cutoff
93 ID_BUTTON_6 = wx.NewId()        #  "                  Lower audio freq cutoff
94 ID_BUTTON_7 = wx.NewId()        #  "                  Frequency 
95 ID_BUTTON_8 = wx.NewId()        #  "                  Volume
96 ID_BUTTON_9 = wx.NewId()        #  "                  Time
97 ID_BUTTON_10 = wx.NewId()       # Time Seek Forwards
98 ID_BUTTON_11 = wx.NewId()       # Time Seek Backwards
99 ID_BUTTON_12 = wx.NewId()       # Automatic Antenna Tune (AT) enable
100 ID_BUTTON_13 = wx.NewId()       # AT Calibrate point
101 ID_BUTTON_14 = wx.NewId()       # AT Reset
102 ID_TEXT_1 = wx.NewId()          # Band Center, USRP ddc Freq
103 ID_SPIN_1 = wx.NewId()          # Frequency display and control
104 ID_SLIDER_1 = wx.NewId()        # Upper audio freq cutoff
105 ID_SLIDER_2 = wx.NewId()        # Lower audio freq cutoff
106 ID_SLIDER_3 = wx.NewId()        # Frequency 
107 ID_SLIDER_4 = wx.NewId()        # Volume
108 ID_SLIDER_5 = wx.NewId()        # Programmable Gain Amp, PGA, RF gain
109 ID_SLIDER_6 = wx.NewId()        # AM Sync carrier level
110 ID_SLIDER_7 = wx.NewId()        # AT control voltage output
111 ID_EXIT = wx.NewId()            # Menu Exit
112
113
114 def pick_subdevice(u):
115     """
116     The user didn't specify a subdevice on the command line.
117     If there's a daughterboard on A, select A.
118     If there's a daughterboard on B, select B.
119     Otherwise, select A.
120     """
121     if u.db(0, 0).dbid() >= 0:       # dbid is < 0 if there's no d'board or a problem
122         return (0, 0)
123     if u.db(1, 0).dbid() >= 0:
124         return (1, 0)
125     return (0, 0)
126
127
128 class MyFrame(wx.Frame):
129     def __init__(self, *args, **kwds):
130         # begin wxGlade: MyFrame.__init__
131         kwds["style"] = wx.DEFAULT_FRAME_STYLE
132         wx.Frame.__init__(self, *args, **kwds)
133         
134         # Menu Bar
135         self.frame_1_menubar = wx.MenuBar()
136         self.SetMenuBar(self.frame_1_menubar)
137         wxglade_tmp_menu = wx.Menu()
138         self.Exit = wx.MenuItem(wxglade_tmp_menu, ID_EXIT, "Exit", "Exit", wx.ITEM_NORMAL)
139         wxglade_tmp_menu.AppendItem(self.Exit)
140         self.frame_1_menubar.Append(wxglade_tmp_menu, "File")
141         # Menu Bar end
142         self.panel_1 = wx.Panel(self, -1)
143         self.button_1 = wx.Button(self, ID_BUTTON_1, "LSB")
144         self.button_2 = wx.Button(self, ID_BUTTON_2, "USB")
145         self.button_3 = wx.Button(self, ID_BUTTON_3, "AM")
146         self.button_4 = wx.Button(self, ID_BUTTON_4, "CW")
147         self.button_5 = wx.ToggleButton(self, ID_BUTTON_5, "Upper")
148         self.slider_1 = wx.Slider(self, ID_SLIDER_1, 0, -15799, 15799, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
149         self.button_6 = wx.ToggleButton(self, ID_BUTTON_6, "Lower")
150         self.slider_2 = wx.Slider(self, ID_SLIDER_2, 0, -15799, 15799, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
151         self.panel_5 = wx.Panel(self, -1)
152         self.label_1 = wx.StaticText(self, -1, " Band\nCenter")
153         self.text_ctrl_1 = wx.TextCtrl(self, ID_TEXT_1, "")
154         self.panel_6 = wx.Panel(self, -1)
155         self.panel_7 = wx.Panel(self, -1)
156         self.panel_2 = wx.Panel(self, -1)
157         self.button_7 = wx.ToggleButton(self, ID_BUTTON_7, "Freq")
158         self.slider_3 = wx.Slider(self, ID_SLIDER_3, 3000, 0, 6000)
159         self.spin_ctrl_1 = wx.SpinCtrl(self, ID_SPIN_1, "", min=0, max=100)
160         self.button_8 = wx.ToggleButton(self, ID_BUTTON_8, "Vol")
161         self.slider_4 = wx.Slider(self, ID_SLIDER_4, 0, 0, 500)
162         self.slider_5 = wx.Slider(self, ID_SLIDER_5, 0, 0, 20)
163         self.button_9 = wx.ToggleButton(self, ID_BUTTON_9, "Time")
164         self.button_11 = wx.Button(self, ID_BUTTON_11, "Rew")
165         self.button_10 = wx.Button(self, ID_BUTTON_10, "Fwd")
166         self.panel_3 = wx.Panel(self, -1)
167         self.label_2 = wx.StaticText(self, -1, "PGA               ")
168         self.panel_4 = wx.Panel(self, -1)
169         self.panel_8 = wx.Panel(self, -1)
170         self.panel_9 = wx.Panel(self, -1)
171         self.label_3 = wx.StaticText(self, -1, "AM Sync\nCarrier")
172         self.slider_6 = wx.Slider(self, ID_SLIDER_6, 50, 0, 200, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
173         self.label_4 = wx.StaticText(self, -1, "Antenna Tune")
174         self.slider_7 = wx.Slider(self, ID_SLIDER_7, 1575, 950, 2200, style=wx.SL_HORIZONTAL|wx.SL_LABELS)
175         self.panel_10 = wx.Panel(self, -1)
176         self.button_12 = wx.ToggleButton(self, ID_BUTTON_12, "Auto Tune")
177         self.button_13 = wx.Button(self, ID_BUTTON_13, "Calibrate")
178         self.button_14 = wx.Button(self, ID_BUTTON_14, "Reset")
179         self.panel_11 = wx.Panel(self, -1)
180         self.panel_12 = wx.Panel(self, -1)
181
182         self.__set_properties()
183         self.__do_layout()
184         # end wxGlade
185
186         parser = OptionParser (option_class=eng_option)
187         parser.add_option ("-c", "--ddc-freq", type="eng_float", default=3.9e6,
188                            help="set Rx DDC frequency to FREQ", metavar="FREQ")
189         parser.add_option ("-a", "--audio_file", default="",
190                            help="audio output file", metavar="FILE")
191         parser.add_option ("-r", "--radio_file", default="",
192                            help="radio output file", metavar="FILE")
193         parser.add_option ("-i", "--input_file", default="",
194                            help="radio input file", metavar="FILE")
195         parser.add_option ("-d", "--decim", type="int", default=250,
196                            help="USRP decimation")
197         parser.add_option("-R", "--rx-subdev-spec", type="subdev", default=None,                          help="select USRP Rx side A or B (default=first one with a daughterboard)")
198         (options, args) = parser.parse_args ()
199
200         self.usrp_center = options.ddc_freq
201         usb_rate = 64e6 / options.decim
202         self.slider_range = usb_rate * 0.9375
203         self.f_lo = self.usrp_center - (self.slider_range/2)
204         self.f_hi = self.usrp_center + (self.slider_range/2)
205         self.af_sample_rate = 32000
206         fir_decim = long (usb_rate / self.af_sample_rate)
207
208         # data point arrays for antenna tuner
209         self.xdata = []
210         self.ydata = []
211
212         self.tb = gr.top_block()
213
214         # radio variables, initial conditions
215         self.frequency = self.usrp_center
216         # these map the frequency slider (0-6000) to the actual range
217         self.f_slider_offset = self.f_lo
218         self.f_slider_scale = 10000/options.decim
219         self.spin_ctrl_1.SetRange(self.f_lo,self.f_hi)
220         self.text_ctrl_1.SetValue(str(int(self.usrp_center)))
221         self.slider_5.SetValue(0) 
222         self.AM_mode = False
223
224         self.slider_3.SetValue((self.frequency-self.f_slider_offset)/self.f_slider_scale)
225         self.spin_ctrl_1.SetValue(int(self.frequency))
226
227         POWERMATE = True
228         try:
229             self.pm = powermate.powermate(self)
230         except:
231             sys.stderr.write("Unable to find PowerMate or Contour Shuttle\n")
232             POWERMATE = False
233
234         if POWERMATE:
235           powermate.EVT_POWERMATE_ROTATE(self, self.on_rotate)
236           powermate.EVT_POWERMATE_BUTTON(self, self.on_pmButton)
237         self.active_button = 7
238
239         # command line options
240         if options.audio_file == "": SAVE_AUDIO_TO_FILE = False
241         else: SAVE_AUDIO_TO_FILE = True
242         if options.radio_file == "": SAVE_RADIO_TO_FILE = False
243         else: SAVE_RADIO_TO_FILE = True
244         if options.input_file == "": self.PLAY_FROM_USRP = True
245         else: self.PLAY_FROM_USRP = False
246
247         if self.PLAY_FROM_USRP:
248            self.src = usrp.source_s(decim_rate=options.decim)
249            if options.rx_subdev_spec is None:
250               options.rx_subdev_spec = pick_subdevice(self.src)
251            self.src.set_mux(usrp.determine_rx_mux_value(self.src, options.rx_subdev_spec))
252            self.subdev = usrp.selected_subdev(self.src, options.rx_subdev_spec)
253            self.src.tune(0, self.subdev, self.usrp_center)
254            self.tune_offset = 0 # -self.usrp_center - self.src.rx_freq(0)
255
256         else:
257            self.src = gr.file_source (gr.sizeof_short,options.input_file)
258            self.tune_offset = 2200 # 2200 works for 3.5-4Mhz band
259
260         # save radio data to a file
261         if SAVE_RADIO_TO_FILE:
262            file = gr.file_sink(gr.sizeof_short, options.radio_file)
263            self.tb.connect (self.src, file)
264
265         # 2nd DDC
266         xlate_taps = gr.firdes.low_pass ( \
267            1.0, usb_rate, 16e3, 4e3, gr.firdes.WIN_HAMMING )
268         self.xlate = gr.freq_xlating_fir_filter_ccf ( \
269            fir_decim, xlate_taps, self.tune_offset, usb_rate )
270
271         # convert rf data in interleaved short int form to complex
272         s2ss = gr.stream_to_streams(gr.sizeof_short,2)
273         s2f1 = gr.short_to_float()
274         s2f2 = gr.short_to_float()
275         src_f2c = gr.float_to_complex()
276         self.tb.connect(self.src,s2ss)
277         self.tb.connect((s2ss,0),s2f1)
278         self.tb.connect((s2ss,1),s2f2)
279         self.tb.connect(s2f1,(src_f2c,0))
280         self.tb.connect(s2f2,(src_f2c,1))
281
282
283         # Complex Audio filter
284         audio_coeffs = gr.firdes.complex_band_pass (
285                 1.0,    # gain
286                 self.af_sample_rate, # sample rate
287                 -3000,    # low cutoff
288                 0,   # high cutoff
289                 100,    # transition
290                 gr.firdes.WIN_HAMMING)  # window
291         self.slider_1.SetValue(0)
292         self.slider_2.SetValue(-3000)
293
294         self.audio_filter = gr.fir_filter_ccc ( 1, audio_coeffs)
295
296         # Main +/- 16Khz spectrum display
297         self.fft = fftsink2.fft_sink_c (self.panel_2, fft_size=512, sample_rate=self.af_sample_rate, average=True, size=(640,240))
298
299         # AM Sync carrier 
300         if AM_SYNC_DISPLAY:
301            self.fft2 = fftsink.fft_sink_c (self.tb, self.panel_9, y_per_div=20, fft_size=512, sample_rate=self.af_sample_rate, average=True, size=(640,240))
302
303         c2f = gr.complex_to_float()
304
305         # AM branch
306         self.sel_am = gr.multiply_const_cc(0)
307         # the following frequencies turn out to be in radians/sample
308         # gr.pll_refout_cc(alpha,beta,min_freq,max_freq)
309         # suggested alpha = X, beta = .25 * X * X
310         pll = gr.pll_refout_cc(.5,.0625,(2.*math.pi*7.5e3/self.af_sample_rate),(2.*math.pi*6.5e3/self.af_sample_rate))
311         self.pll_carrier_scale = gr.multiply_const_cc(complex(10,0))
312         am_det = gr.multiply_cc()
313         # these are for converting +7.5kHz to -7.5kHz
314         # for some reason gr.conjugate_cc() adds noise ??
315         c2f2 = gr.complex_to_float()
316         c2f3 = gr.complex_to_float()
317         f2c = gr.float_to_complex()
318         phaser1 = gr.multiply_const_ff(1)
319         phaser2 = gr.multiply_const_ff(-1)
320
321         # filter for pll generated carrier
322         pll_carrier_coeffs = gr.firdes.complex_band_pass (
323                 2.0,    # gain
324                 self.af_sample_rate, # sample rate
325                 7400,    # low cutoff
326                 7600,   # high cutoff
327                 100,    # transition
328                 gr.firdes.WIN_HAMMING)  # window
329
330         self.pll_carrier_filter = gr.fir_filter_ccc ( 1, pll_carrier_coeffs)
331
332         self.sel_sb = gr.multiply_const_ff(1)
333         combine = gr.add_ff()
334
335         #AGC
336         sqr1 = gr.multiply_ff()
337         intr = gr.iir_filter_ffd ( [.004, 0], [0, .999] )
338         offset = gr.add_const_ff(1)
339         agc = gr.divide_ff()
340
341
342         self.scale = gr.multiply_const_ff(0.00001)
343         dst = audio.sink(long(self.af_sample_rate))
344
345         self.tb.connect(src_f2c,self.xlate,self.fft)
346         self.tb.connect(self.xlate,self.audio_filter,self.sel_am,(am_det,0))
347         self.tb.connect(self.sel_am,pll,self.pll_carrier_scale,self.pll_carrier_filter,c2f3)
348         self.tb.connect((c2f3,0),phaser1,(f2c,0))
349         self.tb.connect((c2f3,1),phaser2,(f2c,1))
350         self.tb.connect(f2c,(am_det,1))
351         self.tb.connect(am_det,c2f2,(combine,0))
352         self.tb.connect(self.audio_filter,c2f,self.sel_sb,(combine,1))
353         if AM_SYNC_DISPLAY:
354           self.tb.connect(self.pll_carrier_filter,self.fft2)
355         self.tb.connect(combine,self.scale)
356         self.tb.connect(self.scale,(sqr1,0))
357         self.tb.connect(self.scale,(sqr1,1))
358         self.tb.connect(sqr1, intr, offset, (agc, 1))
359         self.tb.connect(self.scale,(agc, 0))
360         self.tb.connect(agc,dst)
361
362         if SAVE_AUDIO_TO_FILE:
363           f_out = gr.file_sink(gr.sizeof_short,options.audio_file)
364           sc1 = gr.multiply_const_ff(64000)
365           f2s1 = gr.float_to_short()
366           self.tb.connect(agc,sc1,f2s1,f_out)
367
368         self.tb.start()
369
370         # for mouse position reporting on fft display
371         em.eventManager.Register(self.Mouse, wx.EVT_MOTION, self.fft.win)
372         # and left click to re-tune
373         em.eventManager.Register(self.Click, wx.EVT_LEFT_DOWN, self.fft.win)
374
375         # start a timer to check for web commands
376         if WEB_CONTROL:
377            self.timer = UpdateTimer(self, 1000) # every 1000 mSec, 1 Sec
378
379
380         wx.EVT_BUTTON(self,ID_BUTTON_1,self.set_lsb)
381         wx.EVT_BUTTON(self,ID_BUTTON_2,self.set_usb)
382         wx.EVT_BUTTON(self,ID_BUTTON_3,self.set_am)
383         wx.EVT_BUTTON(self,ID_BUTTON_4,self.set_cw)
384         wx.EVT_BUTTON(self,ID_BUTTON_10,self.fwd)
385         wx.EVT_BUTTON(self,ID_BUTTON_11,self.rew)
386         wx.EVT_BUTTON(self, ID_BUTTON_13, self.AT_calibrate)
387         wx.EVT_BUTTON(self, ID_BUTTON_14, self.AT_reset)
388         wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_5,self.on_button)
389         wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_6,self.on_button)
390         wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_7,self.on_button)
391         wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_8,self.on_button)
392         wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_9,self.on_button)
393         wx.EVT_SLIDER(self,ID_SLIDER_1,self.set_filter)
394         wx.EVT_SLIDER(self,ID_SLIDER_2,self.set_filter)
395         wx.EVT_SLIDER(self,ID_SLIDER_3,self.slide_tune)
396         wx.EVT_SLIDER(self,ID_SLIDER_4,self.set_volume)
397         wx.EVT_SLIDER(self,ID_SLIDER_5,self.set_pga)
398         wx.EVT_SLIDER(self,ID_SLIDER_6,self.am_carrier)
399         wx.EVT_SLIDER(self,ID_SLIDER_7,self.antenna_tune)
400         wx.EVT_SPINCTRL(self,ID_SPIN_1,self.spin_tune)
401
402         wx.EVT_MENU(self, ID_EXIT,  self.TimeToQuit)
403
404     def __set_properties(self):
405         # begin wxGlade: MyFrame.__set_properties
406         self.SetTitle("HF Explorer 2")
407         self.slider_1.SetMinSize((450, 38))
408         self.slider_2.SetMinSize((450, 38))
409         self.panel_2.SetMinSize((640, 240))
410         self.button_7.SetValue(1)
411         self.slider_3.SetMinSize((450, 19))
412         self.slider_4.SetMinSize((275, 19))
413         self.slider_5.SetMinSize((275, 19))
414         if AM_SYNC_DISPLAY:
415            self.panel_9.SetMinSize((640, 240))
416         self.slider_6.SetMinSize((300, 38))
417         self.slider_7.SetMinSize((400, 38))
418         # end wxGlade
419
420     def __do_layout(self):
421         # begin wxGlade: MyFrame.__do_layout
422         sizer_1 = wx.BoxSizer(wx.VERTICAL)
423         grid_sizer_1 = wx.FlexGridSizer(11, 2, 0, 0)
424         sizer_7 = wx.BoxSizer(wx.HORIZONTAL)
425         sizer_5 = wx.BoxSizer(wx.HORIZONTAL)
426         sizer_4 = wx.BoxSizer(wx.HORIZONTAL)
427         sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
428         sizer_6 = wx.BoxSizer(wx.VERTICAL)
429         sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
430         grid_sizer_1.Add(self.panel_1, 1, wx.EXPAND, 0)
431         sizer_2.Add(self.button_1, 0, wx.ADJUST_MINSIZE, 0)
432         sizer_2.Add(self.button_2, 0, wx.ADJUST_MINSIZE, 0)
433         sizer_2.Add(self.button_3, 0, wx.ADJUST_MINSIZE, 0)
434         sizer_2.Add(self.button_4, 0, wx.ADJUST_MINSIZE, 0)
435         grid_sizer_1.Add(sizer_2, 1, wx.EXPAND, 0)
436         grid_sizer_1.Add(self.button_5, 0, wx.ADJUST_MINSIZE, 0)
437         grid_sizer_1.Add(self.slider_1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
438         grid_sizer_1.Add(self.button_6, 0, wx.ADJUST_MINSIZE, 0)
439         grid_sizer_1.Add(self.slider_2, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
440         sizer_6.Add(self.panel_5, 1, wx.EXPAND, 0)
441         sizer_6.Add(self.label_1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0)
442         sizer_6.Add(self.text_ctrl_1, 0, wx.ADJUST_MINSIZE, 0)
443         sizer_6.Add(self.panel_6, 1, wx.EXPAND, 0)
444         sizer_6.Add(self.panel_7, 1, wx.EXPAND, 0)
445         grid_sizer_1.Add(sizer_6, 1, wx.EXPAND, 0)
446         grid_sizer_1.Add(self.panel_2, 1, wx.EXPAND, 0)
447         grid_sizer_1.Add(self.button_7, 0, wx.ADJUST_MINSIZE, 0)
448         sizer_3.Add(self.slider_3, 0, wx.ADJUST_MINSIZE, 0)
449         sizer_3.Add(self.spin_ctrl_1, 0, wx.ADJUST_MINSIZE, 0)
450         grid_sizer_1.Add(sizer_3, 1, wx.EXPAND, 0)
451         grid_sizer_1.Add(self.button_8, 0, wx.ADJUST_MINSIZE, 0)
452         sizer_4.Add(self.slider_4, 0, wx.ADJUST_MINSIZE, 0)
453         sizer_4.Add(self.slider_5, 0, wx.ADJUST_MINSIZE, 0)
454         grid_sizer_1.Add(sizer_4, 1, wx.EXPAND, 0)
455         grid_sizer_1.Add(self.button_9, 0, wx.ADJUST_MINSIZE, 0)
456         sizer_5.Add(self.button_11, 0, wx.ADJUST_MINSIZE, 0)
457         sizer_5.Add(self.button_10, 0, wx.ADJUST_MINSIZE, 0)
458         sizer_5.Add(self.panel_3, 1, wx.EXPAND, 0)
459         sizer_5.Add(self.label_2, 0, wx.ADJUST_MINSIZE, 0)
460         sizer_5.Add(self.panel_4, 1, wx.EXPAND, 0)
461         grid_sizer_1.Add(sizer_5, 1, wx.EXPAND, 0)
462         grid_sizer_1.Add(self.panel_8, 1, wx.EXPAND, 0)
463         grid_sizer_1.Add(self.panel_9, 1, wx.EXPAND, 0)
464         grid_sizer_1.Add(self.label_3, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
465         grid_sizer_1.Add(self.slider_6, 0, wx.ADJUST_MINSIZE, 0)
466         grid_sizer_1.Add(self.label_4, 0, wx.ALIGN_BOTTOM|wx.ADJUST_MINSIZE, 0)
467         grid_sizer_1.Add(self.slider_7, 0, wx.ADJUST_MINSIZE, 0)
468         grid_sizer_1.Add(self.panel_10, 1, wx.EXPAND, 0)
469         sizer_7.Add(self.button_12, 0, wx.ADJUST_MINSIZE, 0)
470         sizer_7.Add(self.button_13, 0, wx.ADJUST_MINSIZE, 0)
471         sizer_7.Add(self.button_14, 0, wx.ADJUST_MINSIZE, 0)
472         sizer_7.Add(self.panel_11, 1, wx.EXPAND, 0)
473         sizer_7.Add(self.panel_12, 1, wx.EXPAND, 0)
474         grid_sizer_1.Add(sizer_7, 1, wx.EXPAND, 0)
475         sizer_1.Add(grid_sizer_1, 1, wx.EXPAND, 0)
476         self.SetAutoLayout(True)
477         self.SetSizer(sizer_1)
478         sizer_1.Fit(self)
479         sizer_1.SetSizeHints(self)
480         self.Layout()
481         # end wxGlade
482
483     # Menu exit
484     def TimeToQuit(self, event):
485         self.tb.stop()
486         self.Close(True)
487
488     # Powermate being turned
489     def on_rotate(self, event):
490         if self.active_button == 5:
491            self.slider_1.SetValue(self.slider_1.GetValue()+event.delta)
492            if self.slider_2.GetValue() > (self.slider_1.GetValue() - 200) :
493               self.slider_2.SetValue(self.slider_1.GetValue() - 200)
494            self.filter()
495         if self.active_button == 6:
496            self.slider_2.SetValue(self.slider_2.GetValue()+event.delta)
497            if self.slider_1.GetValue() < (self.slider_2.GetValue() + 200) :
498               self.slider_1.SetValue(self.slider_2.GetValue() + 200)
499            self.filter()
500         if self.active_button == 7:
501            new = max(0, min(6000, self.slider_3.GetValue() + event.delta))
502            self.slider_3.SetValue(new)
503            self.frequency = (self.f_slider_scale * new) + self.f_slider_offset
504            self.spin_ctrl_1.SetValue(self.frequency)
505            if self.AM_mode == False:
506              self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset))
507            else:
508              self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3))
509            if self.button_12.GetValue():
510               self.auto_antenna_tune()
511         if self.active_button == 8:
512            new = max(0, min(500, self.slider_4.GetValue() + event.delta))
513            self.slider_4.SetValue(new)
514            self.scale.set_k(math.pow(10.,((self.slider_4.GetValue()-500.)/100.)))
515         if self.active_button == 9:
516            if self.PLAY_FROM_USRP == False:
517               if event.delta == -1:
518                  self.src.seek(-1000000,gr.SEEK_CUR)
519               elif event.delta == 1:
520                  self.src.seek(1000000,gr.SEEK_CUR)
521
522
523     # Powermate pressed to switch controlled function
524     def on_pmButton(self, event):
525         if event.value == 0:
526            if self.active_button == 5:
527               self.active_button = 6
528               self.button_5.SetValue(False)
529               self.button_6.SetValue(True)
530            elif self.active_button == 6:
531               self.active_button = 7
532               self.button_6.SetValue(False)
533               self.button_7.SetValue(True)
534            elif self.active_button == 7:
535               self.active_button = 8
536               self.button_7.SetValue(False)
537               self.button_8.SetValue(True)
538            elif self.active_button == 8:
539               self.active_button = 9
540               self.button_8.SetValue(False)
541               self.button_9.SetValue(True)
542            elif self.active_button == 9:
543               self.active_button = 5
544               self.button_9.SetValue(False)
545               self.button_5.SetValue(True)
546
547     # Clicking one PM control button turns the rest off
548     def on_button(self, event):
549         id = event.GetId()
550         if id == ID_BUTTON_5:
551            self.active_button = 5
552            self.button_6.SetValue(False)
553            self.button_7.SetValue(False)
554            self.button_8.SetValue(False)
555            self.button_9.SetValue(False)
556         if id == ID_BUTTON_6:
557            self.active_button = 6
558            self.button_5.SetValue(False)
559            self.button_7.SetValue(False)
560            self.button_8.SetValue(False)
561            self.button_9.SetValue(False)
562         if id == ID_BUTTON_7:
563            self.active_button = 7
564            self.button_5.SetValue(False)
565            self.button_6.SetValue(False)
566            self.button_8.SetValue(False)
567            self.button_9.SetValue(False)
568         if id == ID_BUTTON_8:
569            self.active_button = 8
570            self.button_5.SetValue(False)
571            self.button_6.SetValue(False)
572            self.button_7.SetValue(False)
573            self.button_9.SetValue(False)
574         if id == ID_BUTTON_9:
575            self.active_button = 9
576            self.button_5.SetValue(False)
577            self.button_6.SetValue(False)
578            self.button_7.SetValue(False)
579            self.button_8.SetValue(False)
580
581     # Make sure filter settings are legal
582     def set_filter(self, event):
583         slider = event.GetId()
584         slider1 = self.slider_1.GetValue()
585         slider2 = self.slider_2.GetValue()
586         if slider == ID_SLIDER_1:
587            if slider2 > (self.slider_1.GetValue() - 200) :
588               self.slider_2.SetValue(slider1 - 200)
589         elif slider == ID_SLIDER_2:
590            if slider1 < (self.slider_2.GetValue() + 200) :
591               self.slider_1.SetValue(slider2 + 200)
592         self.filter()
593
594     # Calculate taps and apply
595     def filter(self):
596         audio_coeffs = gr.firdes.complex_band_pass (
597                 1.0,    # gain
598                 self.af_sample_rate, # sample rate
599                 self.slider_2.GetValue(),    # low cutoff
600                 self.slider_1.GetValue(),   # high cutoff
601                 100,    # transition
602                 gr.firdes.WIN_HAMMING)  # window
603         self.audio_filter.set_taps(audio_coeffs)
604
605     def set_lsb(self, event):
606         self.AM_mode = False
607         self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset))
608         self.sel_sb.set_k(1)
609         self.sel_am.set_k(0)
610         self.slider_1.SetValue(0)
611         self.slider_2.SetValue(-3000)
612         self.filter()
613
614     def set_usb(self, event):
615         self.AM_mode = False
616         self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset))
617         self.sel_sb.set_k(1)
618         self.sel_am.set_k(0)
619         self.slider_1.SetValue(3000)
620         self.slider_2.SetValue(0)
621         self.filter()
622
623     def set_am(self, event):
624         self.AM_mode = True
625         self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3))
626         self.sel_sb.set_k(0)
627         self.sel_am.set_k(1)
628         self.slider_1.SetValue(12500)
629         self.slider_2.SetValue(2500)
630         self.filter()
631
632     def set_cw(self, event):
633         self.AM_mode = False
634         self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset))
635         self.AM_mode = False
636         self.sel_sb.set_k(1)
637         self.sel_am.set_k(0)
638         self.slider_1.SetValue(-400)
639         self.slider_2.SetValue(-800)
640         self.filter()
641
642     def set_volume(self, event):
643         self.scale.set_k(math.pow(10.,((self.slider_4.GetValue()-500.)/100.)))
644
645     def set_pga(self,event):
646         if self.PLAY_FROM_USRP:
647            self.subdev.set_gain(self.slider_5.GetValue())
648
649     def slide_tune(self, event):
650         self.frequency = (self.f_slider_scale * self.slider_3.GetValue()) + self.f_slider_offset
651         if self.AM_mode == False:
652           self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset))
653         else:
654           self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3))
655         self.spin_ctrl_1.SetValue(self.frequency)
656         if self.button_12.GetValue():
657            self.auto_antenna_tune()
658
659     def spin_tune(self, event):
660         self.frequency = self.spin_ctrl_1.GetValue()
661         if self.AM_mode == False:
662            self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset))
663         else:
664            self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3))
665         self.slider_3.SetValue(int((self.frequency-self.f_slider_offset)/self.f_slider_scale))
666         if self.button_12.GetValue():
667            self.auto_antenna_tune()
668
669     # Seek forwards in file
670     def fwd(self, event):
671         if self.PLAY_FROM_USRP == False:
672            self.src.seek(10000000,gr.SEEK_CUR)
673
674     # Seek backwards in file
675     def rew(self, event):
676         if self.PLAY_FROM_USRP == False:
677            self.src.seek(-10000000,gr.SEEK_CUR)
678
679     # Mouse over fft display - show frequency in tooltip
680     def Mouse(self,event):
681         if self.AM_mode:
682            fRel = ( event.GetX() - 330. ) / 14.266666 - 7.5
683         else:
684            fRel = ( event.GetX() - 330. ) / 14.266666 
685         self.fft.win.SetToolTip(wx.ToolTip(eng_notation.num_to_str(self.frequency + (fRel*1e3))))
686
687     # Mouse clicked on fft display - change frequency 
688     def Click(self,event):
689         fRel = ( event.GetX() - 330. ) / 14.266666
690         if self.AM_mode == False:
691            self.frequency = self.frequency + (fRel*1e3)
692         else:
693            self.frequency = self.frequency + (fRel*1e3) - 7.5e3
694         self.spin_ctrl_1.SetValue(int(self.frequency))
695         self.slider_3.SetValue(int((self.frequency-self.f_slider_offset)/self.f_slider_scale))
696         if self.AM_mode == False:
697            self.xlate.set_center_freq ( self.usrp_center - ( self.frequency - self.tune_offset ))
698         else:
699            self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3))
700
701     # Set power of AM sync carrier
702     def am_carrier(self,event):
703         scale = math.pow(10,(self.slider_6.GetValue())/50.)
704         self.pll_carrier_scale.set_k(complex(scale,0))
705
706     # Reset AT data and start calibrate over
707     def AT_reset(self, event):
708         self.xdata = []
709         self.ydata = []
710
711     # Save AT setting for a particular frequency
712     def AT_calibrate(self, event):
713         self.xdata.append(float(self.frequency))
714         self.ydata.append(self.slider_7.GetValue())
715         if len(self.xdata) > 1:
716           self.m = []
717           self.b = []
718           for i in range(0,len(self.xdata)-1):
719             self.m.append( (self.ydata[i+1] - self.ydata[i]) / (self.xdata[i+1] - self.xdata[i]) )
720             self.b.append( self.ydata[i] - self.m[i] * self.xdata[i] )
721
722     # Lookup calibrated points and calculate interpolated antenna tune voltage.
723     # This is to automatically tune a narrowband loop antenna when the freq
724     # is changed, to keep signals peaked.
725     def auto_antenna_tune(self):
726         for i in range(0,len(self.xdata)-1):
727           if (self.frequency > self.xdata[i]) & (self.frequency < self.xdata[i+1]):
728             self.slider_7.SetValue(self.m[i]*self.frequency + self.b[i])
729         self.antenna_tune(0)
730
731     # Slider to set loop antenna capacitance
732     def antenna_tune(self, evt):
733         if self.PLAY_FROM_USRP:
734            self.src.write_aux_dac(0,3,self.slider_7.GetValue())
735
736     # Timer events - check for web commands
737     def OnUpdate(self):
738       cmds = os.listdir("/var/www/cgi-bin/commands/")
739       if cmds!=[]:
740         if cmds[0]=='chfreq':
741           fd=open("/var/www/cgi-bin/commands/chfreq","r")
742           new=fd.readline()
743           fd.close()
744           if new!='':
745             os.unlink("/var/www/cgi-bin/commands/chfreq")
746             if ( int(new) >= self.f_lo ) & ( int(new) <= self.f_hi ):
747                self.frequency = int(new)
748                self.slider_3.SetValue(( self.frequency - self.f_slider_offset) / self.f_slider_scale )
749                self.spin_ctrl_1.SetValue(self.frequency)
750                if self.button_12.GetValue():
751                  self.auto_antenna_tune()
752                if self.AM_mode:
753                  self.xlate.set_center_freq ( self.usrp_center - ( self.frequency - self.tune_offset - 7.5e3 ))
754                else:
755                  self.xlate.set_center_freq ( self.usrp_center - ( self.frequency - self.tune_offset ))
756
757         if cmds[0]=='chvolume':
758           fd=open("/var/www/cgi-bin/commands/chvolume","r")
759           new=fd.readline()
760           fd.close()
761           if new!='':
762             os.unlink("/var/www/cgi-bin/commands/chvolume")
763             if ( int(new) >= 0 ) & ( int(new) <= 500 ):
764                self.volume = int(new)
765                self.slider_4.SetValue(self.volume)
766                self.scale.set_k(math.pow(10.,((self.slider_4.GetValue()-500.)/100.)))
767
768       else: # no new web commands, update state
769         fh = open("/var/www/cgi-bin/state/freq","w")
770         fh.write(str(int(self.frequency))+'\n')
771         fh.close()
772         fh = open("/var/www/cgi-bin/state/volume","w")
773         fh.write(str(self.slider_4.GetValue())+'\n')
774         fh.close()
775
776
777 # end of class MyFrame
778
779 # wx.Timer to check for web updates
780 class UpdateTimer(wx.Timer):
781     def __init__(self, target, dur=1000):
782         wx.Timer.__init__(self)
783         self.target = target
784         self.Start(dur)
785
786     def Notify(self):
787         """Called every timer interval"""
788         if self.target:
789             self.target.OnUpdate()
790
791
792 class MyApp(wx.App):
793   def OnInit(self):
794     frame = MyFrame(None, -1, "HF Explorer 2")
795     frame.Show(True)
796     self.SetTopWindow(frame)
797     return True
798
799 app = MyApp(0)
800 app.MainLoop()
801