diff options
author | jcorgan | 2006-08-03 04:51:51 +0000 |
---|---|---|
committer | jcorgan | 2006-08-03 04:51:51 +0000 |
commit | 5d69a524f81f234b3fbc41d49ba18d6f6886baba (patch) | |
tree | b71312bf7f1e8d10fef0f3ac6f28784065e73e72 /gnuradio-examples/python/apps/hf_explorer | |
download | gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.tar.gz gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.tar.bz2 gnuradio-5d69a524f81f234b3fbc41d49ba18d6f6886baba.zip |
Houston, we have a trunk.
git-svn-id: http://gnuradio.org/svn/gnuradio/trunk@3122 221aa14e-8319-0410-a670-987f0aec2ac5
Diffstat (limited to 'gnuradio-examples/python/apps/hf_explorer')
-rw-r--r-- | gnuradio-examples/python/apps/hf_explorer/README | 48 | ||||
-rwxr-xr-x | gnuradio-examples/python/apps/hf_explorer/hfx2.py | 786 | ||||
-rw-r--r-- | gnuradio-examples/python/apps/hf_explorer/hfx_help | 180 |
3 files changed, 1014 insertions, 0 deletions
diff --git a/gnuradio-examples/python/apps/hf_explorer/README b/gnuradio-examples/python/apps/hf_explorer/README new file mode 100644 index 000000000..5f780b3d9 --- /dev/null +++ b/gnuradio-examples/python/apps/hf_explorer/README @@ -0,0 +1,48 @@ +hfx.py is meant to be a full-featured Long Wave / Medium Wave +and Short Wave (250kHz to 30Mhz) AM and Single Sideband receiver. +It uses the USRP with a Basic RX board, and will need an +antenna and some preamps, about 30db gain will work. See the +'Help' menu or hfx_help for more info. + +---------------------------------------------------------- + +hfx2.py is a major revision built about complex fir filter +coeffecients ability and cleaner python script. Inherits +most features from hfx.py - Powermate knob supported but +not required, tooltip frequency display, single click +tuning, AGC, record to disk, play from disk and record audio. +New feature is ability to tailor the audio passband with two +sliders over the spectrum display. The sliders almost align +with the actual frequency. Preset filter settings for LSB +(-3000 to 0kHz), USB (0 to +3000kHz), CW (-400 to -800Hz) +and AM (-5kHz from carrier to +5kHz). + +AM now switches in a synchronous PLL detector with the +carriers at 7.5kHz. The PLL carrier is displayed in the +bottom display and helps show where on the upper spectrum +the demodulated signal lies. Everything gets shifted up +7.5kHz in AM, center frequency, tooltips, etc. The target +AM carrier needs to be closely tuned in, it will have a +hollow sound untill it is locked, and then the PLL carrier +in the bottom display will jump up and remain relatively +constant. There is a slider "AM sync carrier" to play with +different levels to mix with the signal for demodulation. +The filter in AM is preset to 2500/12500 (7.5kHz +/- 5kHz) +and is handy for removing adjacent channel interference. +Change AM_SYNC_DISPLAY in script for whether to show AM +Sync carrier or not. +Run with "-h" for command line help with setting USRP +ddc center frequency, decimation, rf data record, playback +and audio data recording. + +There are some controls for controlling a varactor and +tuning an antenna - just ignore them unless you want +to build a voltage tuned antenna to track frequency. + +There is also code for Web based control of frequency and +volume - so I can tune the radio with an Ipaq from bed. +Disabled by default - it takes a web server, some +directories and scripts to use. + + + diff --git a/gnuradio-examples/python/apps/hf_explorer/hfx2.py b/gnuradio-examples/python/apps/hf_explorer/hfx2.py new file mode 100755 index 000000000..c09f962cf --- /dev/null +++ b/gnuradio-examples/python/apps/hf_explorer/hfx2.py @@ -0,0 +1,786 @@ +#!/usr/bin/env python +# -*- coding: ANSI_X3.4-1968 -*- +# generated by wxGlade 0.4 on Tue Mar 14 10:16:06 2006 +# +# Copyright 2006 Free Software Foundation, Inc. +# +# This file is part of GNU Radio +# +# GNU Radio is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# GNU Radio is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GNU Radio; see the file COPYING. If not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307, USA. +# +#----------------------------------------------------------------- +# +# +-->(fft) +# | +# (src)->(xlate)--+->(audio filter)--+-->(sel_am)-+--------------+ +# | | | +# | (pll) | +# | | | +# | (pll_carrier_scale) | +# | | | +# | (pll_carrier_filter) | +# | | | +# | +--(fft2) | +# | | | +# | +--(c2f3)--+ | +# | | | | +# | (phaser1) (phaser2) | +# | | | | +# | +--(f2c)---+ | +# | | V +# V +---------->(am_det) +# (c2f) | +# | (c2f2) +# | | +# +-->(sel_sb)------------>(combine) +# | +# V +# +--------------------------(scale) +# | | +# | | +# | +++ +# V | | +# (agc)<--(offset)<--(intr)<---(sqr1) +# | +# V +# (dst) +# +#---------------------------------------------------------------------- +# +# Versions 2.2.1 adds loop antenna automatic tuner +# +# 2.3.1 adds web control, made AM Sync display optional, +# added more comments. +# +# 2.4.1 updates usrp interface to support auto subdev + +# Web server control disabled by default. Do not enable +# until directory structure and scripts are in place. +WEB_CONTROL = False + +# Controls display of AM Sync Carrier - turn off for smaller +# window if not needed +AM_SYNC_DISPLAY = False + +import os, wx, sys, math +import wx.lib.evtmgr as em +from gnuradio.wxgui import powermate, fftsink +from gnuradio import gr, audio, eng_notation, usrp, gru +from gnuradio.eng_option import eng_option +from optparse import OptionParser + +ID_BUTTON_1 = wx.NewId() # LSB button +ID_BUTTON_2 = wx.NewId() # USB +ID_BUTTON_3 = wx.NewId() # AM +ID_BUTTON_4 = wx.NewId() # CW +ID_BUTTON_5 = wx.NewId() # Powermate controls: Upper audio freq cutoff +ID_BUTTON_6 = wx.NewId() # " Lower audio freq cutoff +ID_BUTTON_7 = wx.NewId() # " Frequency +ID_BUTTON_8 = wx.NewId() # " Volume +ID_BUTTON_9 = wx.NewId() # " Time +ID_BUTTON_10 = wx.NewId() # Time Seek Forwards +ID_BUTTON_11 = wx.NewId() # Time Seek Backwards +ID_BUTTON_12 = wx.NewId() # Automatic Antenna Tune (AT) enable +ID_BUTTON_13 = wx.NewId() # AT Calibrate point +ID_BUTTON_14 = wx.NewId() # AT Reset +ID_TEXT_1 = wx.NewId() # Band Center, USRP ddc Freq +ID_SPIN_1 = wx.NewId() # Frequency display and control +ID_SLIDER_1 = wx.NewId() # Upper audio freq cutoff +ID_SLIDER_2 = wx.NewId() # Lower audio freq cutoff +ID_SLIDER_3 = wx.NewId() # Frequency +ID_SLIDER_4 = wx.NewId() # Volume +ID_SLIDER_5 = wx.NewId() # Programmable Gain Amp, PGA, RF gain +ID_SLIDER_6 = wx.NewId() # AM Sync carrier level +ID_SLIDER_7 = wx.NewId() # AT control voltage output +ID_EXIT = wx.NewId() # Menu Exit + + +def pick_subdevice(u): + """ + The user didn't specify a subdevice on the command line. + If there's a daughterboard on A, select A. + If there's a daughterboard on B, select B. + Otherwise, select A. + """ + if u.db[0][0].dbid() >= 0: # dbid is < 0 if there's no d'board or a problem + return (0, 0) + if u.db[1][0].dbid() >= 0: + return (1, 0) + return (0, 0) + + +class MyFrame(wx.Frame): + def __init__(self, *args, **kwds): + # begin wxGlade: MyFrame.__init__ + kwds["style"] = wx.DEFAULT_FRAME_STYLE + wx.Frame.__init__(self, *args, **kwds) + + # Menu Bar + self.frame_1_menubar = wx.MenuBar() + self.SetMenuBar(self.frame_1_menubar) + wxglade_tmp_menu = wx.Menu() + self.Exit = wx.MenuItem(wxglade_tmp_menu, ID_EXIT, "Exit", "Exit", wx.ITEM_NORMAL) + wxglade_tmp_menu.AppendItem(self.Exit) + self.frame_1_menubar.Append(wxglade_tmp_menu, "File") + # Menu Bar end + self.panel_1 = wx.Panel(self, -1) + self.button_1 = wx.Button(self, ID_BUTTON_1, "LSB") + self.button_2 = wx.Button(self, ID_BUTTON_2, "USB") + self.button_3 = wx.Button(self, ID_BUTTON_3, "AM") + self.button_4 = wx.Button(self, ID_BUTTON_4, "CW") + self.button_5 = wx.ToggleButton(self, ID_BUTTON_5, "Upper") + self.slider_1 = wx.Slider(self, ID_SLIDER_1, 0, -15799, 15799, style=wx.SL_HORIZONTAL|wx.SL_LABELS) + self.button_6 = wx.ToggleButton(self, ID_BUTTON_6, "Lower") + self.slider_2 = wx.Slider(self, ID_SLIDER_2, 0, -15799, 15799, style=wx.SL_HORIZONTAL|wx.SL_LABELS) + self.panel_5 = wx.Panel(self, -1) + self.label_1 = wx.StaticText(self, -1, " Band\nCenter") + self.text_ctrl_1 = wx.TextCtrl(self, ID_TEXT_1, "") + self.panel_6 = wx.Panel(self, -1) + self.panel_7 = wx.Panel(self, -1) + self.panel_2 = wx.Panel(self, -1) + self.button_7 = wx.ToggleButton(self, ID_BUTTON_7, "Freq") + self.slider_3 = wx.Slider(self, ID_SLIDER_3, 3000, 0, 6000) + self.spin_ctrl_1 = wx.SpinCtrl(self, ID_SPIN_1, "", min=0, max=100) + self.button_8 = wx.ToggleButton(self, ID_BUTTON_8, "Vol") + self.slider_4 = wx.Slider(self, ID_SLIDER_4, 0, 0, 500) + self.slider_5 = wx.Slider(self, ID_SLIDER_5, 0, 0, 20) + self.button_9 = wx.ToggleButton(self, ID_BUTTON_9, "Time") + self.button_11 = wx.Button(self, ID_BUTTON_11, "Rew") + self.button_10 = wx.Button(self, ID_BUTTON_10, "Fwd") + self.panel_3 = wx.Panel(self, -1) + self.label_2 = wx.StaticText(self, -1, "PGA ") + self.panel_4 = wx.Panel(self, -1) + self.panel_8 = wx.Panel(self, -1) + self.panel_9 = wx.Panel(self, -1) + self.label_3 = wx.StaticText(self, -1, "AM Sync\nCarrier") + self.slider_6 = wx.Slider(self, ID_SLIDER_6, 50, 0, 200, style=wx.SL_HORIZONTAL|wx.SL_LABELS) + self.label_4 = wx.StaticText(self, -1, "Antenna Tune") + self.slider_7 = wx.Slider(self, ID_SLIDER_7, 1575, 950, 2200, style=wx.SL_HORIZONTAL|wx.SL_LABELS) + self.panel_10 = wx.Panel(self, -1) + self.button_12 = wx.ToggleButton(self, ID_BUTTON_12, "Auto Tune") + self.button_13 = wx.Button(self, ID_BUTTON_13, "Calibrate") + self.button_14 = wx.Button(self, ID_BUTTON_14, "Reset") + self.panel_11 = wx.Panel(self, -1) + self.panel_12 = wx.Panel(self, -1) + + self.__set_properties() + self.__do_layout() + # end wxGlade + + parser = OptionParser (option_class=eng_option) + parser.add_option ("-c", "--ddc-freq", type="eng_float", default=3.9e6, + help="set Rx DDC frequency to FREQ", metavar="FREQ") + parser.add_option ("-a", "--audio_file", default="", + help="audio output file", metavar="FILE") + parser.add_option ("-r", "--radio_file", default="", + help="radio output file", metavar="FILE") + parser.add_option ("-i", "--input_file", default="", + help="radio input file", metavar="FILE") + parser.add_option ("-d", "--decim", type="int", default=250, + help="USRP decimation") + 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)") + (options, args) = parser.parse_args () + + self.usrp_center = options.ddc_freq + usb_rate = 64e6 / options.decim + self.slider_range = usb_rate * 0.9375 + self.f_lo = self.usrp_center - (self.slider_range/2) + self.f_hi = self.usrp_center + (self.slider_range/2) + self.af_sample_rate = 32000 + fir_decim = long (usb_rate / self.af_sample_rate) + + # data point arrays for antenna tuner + self.xdata = [] + self.ydata = [] + + self.fg = gr.flow_graph() + + # radio variables, initial conditions + self.frequency = self.usrp_center + # these map the frequency slider (0-6000) to the actual range + self.f_slider_offset = self.f_lo + self.f_slider_scale = 10000/options.decim + self.spin_ctrl_1.SetRange(self.f_lo,self.f_hi) + self.text_ctrl_1.SetValue(str(int(self.usrp_center))) + self.slider_5.SetValue(0) + self.AM_mode = False + + self.slider_3.SetValue((self.frequency-self.f_slider_offset)/self.f_slider_scale) + self.spin_ctrl_1.SetValue(int(self.frequency)) + + POWERMATE = True + try: + self.pm = powermate.powermate(self) + except: + sys.stderr.write("Unable to find PowerMate or Contour Shuttle\n") + POWERMATE = False + + if POWERMATE: + powermate.EVT_POWERMATE_ROTATE(self, self.on_rotate) + powermate.EVT_POWERMATE_BUTTON(self, self.on_pmButton) + self.active_button = 7 + + # command line options + if options.audio_file == "": SAVE_AUDIO_TO_FILE = False + else: SAVE_AUDIO_TO_FILE = True + if options.radio_file == "": SAVE_RADIO_TO_FILE = False + else: SAVE_RADIO_TO_FILE = True + if options.input_file == "": self.PLAY_FROM_USRP = True + else: self.PLAY_FROM_USRP = False + + if self.PLAY_FROM_USRP: + self.src = usrp.source_c(decim_rate=options.decim) + if options.rx_subdev_spec is None: + options.rx_subdev_spec = pick_subdevice(self.src) + self.src.set_mux(usrp.determine_rx_mux_value(self.src, options.rx_subdev_spec)) + self.subdev = usrp.selected_subdev(self.src, options.rx_subdev_spec) + self.src.tune(0, self.subdev, self.usrp_center) + self.tune_offset = 0 # -self.usrp_center - self.src.rx_freq(0) + + else: + self.src = gr.file_source (gr.sizeof_gr_complex,options.input_file) + self.tune_offset = 2200 # 2200 works for 3.5-4Mhz band + + # save radio data to a file + if SAVE_RADIO_TO_FILE: + file = gr.file_sink(gr.sizeof_gr_complex, options.radio_file) + self.fg.connect (self.src, file) + + # 2nd DDC + xlate_taps = gr.firdes.low_pass ( \ + 1.0, usb_rate, 16e3, 4e3, gr.firdes.WIN_HAMMING ) + self.xlate = gr.freq_xlating_fir_filter_ccf ( \ + fir_decim, xlate_taps, self.tune_offset, usb_rate ) + + # Complex Audio filter + audio_coeffs = gr.firdes.complex_band_pass ( + 1.0, # gain + self.af_sample_rate, # sample rate + -3000, # low cutoff + 0, # high cutoff + 100, # transition + gr.firdes.WIN_HAMMING) # window + self.slider_1.SetValue(0) + self.slider_2.SetValue(-3000) + + self.audio_filter = gr.fir_filter_ccc ( 1, audio_coeffs) + + # Main +/- 16Khz spectrum display + self.fft = fftsink.fft_sink_c (self.fg, self.panel_2, fft_size=512, sample_rate=self.af_sample_rate, average=True, size=(640,240)) + + # AM Sync carrier + if AM_SYNC_DISPLAY: + self.fft2 = fftsink.fft_sink_c (self.fg, self.panel_9, y_per_div=20, fft_size=512, sample_rate=self.af_sample_rate, average=True, size=(640,240)) + + c2f = gr.complex_to_float() + + # AM branch + self.sel_am = gr.multiply_const_cc(0) + # the following frequencies turn out to be in radians/sample + # gr.pll_refout_cc(alpha,beta,min_freq,max_freq) + # suggested alpha = X, beta = .25 * X * X + 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)) + self.pll_carrier_scale = gr.multiply_const_cc(complex(10,0)) + am_det = gr.multiply_cc() + # these are for converting +7.5kHz to -7.5kHz + # for some reason gr.conjugate_cc() adds noise ?? + c2f2 = gr.complex_to_float() + c2f3 = gr.complex_to_float() + f2c = gr.float_to_complex() + phaser1 = gr.multiply_const_ff(1) + phaser2 = gr.multiply_const_ff(-1) + + # filter for pll generated carrier + pll_carrier_coeffs = gr.firdes.complex_band_pass ( + 2.0, # gain + self.af_sample_rate, # sample rate + 7400, # low cutoff + 7600, # high cutoff + 100, # transition + gr.firdes.WIN_HAMMING) # window + + self.pll_carrier_filter = gr.fir_filter_ccc ( 1, pll_carrier_coeffs) + + self.sel_sb = gr.multiply_const_ff(1) + combine = gr.add_ff() + + #AGC + sqr1 = gr.multiply_ff() + intr = gr.iir_filter_ffd ( [.004, 0], [0, .999] ) + offset = gr.add_const_ff(1) + agc = gr.divide_ff() + + + self.scale = gr.multiply_const_ff(0.00001) + dst = audio.sink(long(self.af_sample_rate)) + + self.fg.connect(self.src,self.xlate,self.fft) + self.fg.connect(self.xlate,self.audio_filter,self.sel_am,(am_det,0)) + self.fg.connect(self.sel_am,pll,self.pll_carrier_scale,self.pll_carrier_filter,c2f3) + self.fg.connect((c2f3,0),phaser1,(f2c,0)) + self.fg.connect((c2f3,1),phaser2,(f2c,1)) + self.fg.connect(f2c,(am_det,1)) + self.fg.connect(am_det,c2f2,(combine,0)) + self.fg.connect(self.audio_filter,c2f,self.sel_sb,(combine,1)) + if AM_SYNC_DISPLAY: + self.fg.connect(self.pll_carrier_filter,self.fft2) + self.fg.connect(combine,self.scale) + self.fg.connect(self.scale,(sqr1,0)) + self.fg.connect(self.scale,(sqr1,1)) + self.fg.connect(sqr1, intr, offset, (agc, 1)) + self.fg.connect(self.scale,(agc, 0)) + self.fg.connect(agc,dst) + + if SAVE_AUDIO_TO_FILE: + f_out = gr.file_sink(gr.sizeof_short,options.audio_file) + sc1 = gr.multiply_const_ff(64000) + f2s1 = gr.float_to_short() + self.fg.connect(agc,sc1,f2s1,f_out) + + self.fg.start() + + # for mouse position reporting on fft display + em.eventManager.Register(self.Mouse, wx.EVT_MOTION, self.fft.win) + # and left click to re-tune + em.eventManager.Register(self.Click, wx.EVT_LEFT_DOWN, self.fft.win) + + # start a timer to check for web commands + if WEB_CONTROL: + self.timer = UpdateTimer(self, 1000) # every 1000 mSec, 1 Sec + + + wx.EVT_BUTTON(self,ID_BUTTON_1,self.set_lsb) + wx.EVT_BUTTON(self,ID_BUTTON_2,self.set_usb) + wx.EVT_BUTTON(self,ID_BUTTON_3,self.set_am) + wx.EVT_BUTTON(self,ID_BUTTON_4,self.set_cw) + wx.EVT_BUTTON(self,ID_BUTTON_10,self.fwd) + wx.EVT_BUTTON(self,ID_BUTTON_11,self.rew) + wx.EVT_BUTTON(self, ID_BUTTON_13, self.AT_calibrate) + wx.EVT_BUTTON(self, ID_BUTTON_14, self.AT_reset) + wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_5,self.on_button) + wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_6,self.on_button) + wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_7,self.on_button) + wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_8,self.on_button) + wx.EVT_TOGGLEBUTTON(self,ID_BUTTON_9,self.on_button) + wx.EVT_SLIDER(self,ID_SLIDER_1,self.set_filter) + wx.EVT_SLIDER(self,ID_SLIDER_2,self.set_filter) + wx.EVT_SLIDER(self,ID_SLIDER_3,self.slide_tune) + wx.EVT_SLIDER(self,ID_SLIDER_4,self.set_volume) + wx.EVT_SLIDER(self,ID_SLIDER_5,self.set_pga) + wx.EVT_SLIDER(self,ID_SLIDER_6,self.am_carrier) + wx.EVT_SLIDER(self,ID_SLIDER_7,self.antenna_tune) + wx.EVT_SPINCTRL(self,ID_SPIN_1,self.spin_tune) + + wx.EVT_MENU(self, ID_EXIT, self.TimeToQuit) + + def __set_properties(self): + # begin wxGlade: MyFrame.__set_properties + self.SetTitle("HF Explorer 2") + self.slider_1.SetMinSize((450, 38)) + self.slider_2.SetMinSize((450, 38)) + self.panel_2.SetMinSize((640, 240)) + self.button_7.SetValue(1) + self.slider_3.SetMinSize((450, 19)) + self.slider_4.SetMinSize((275, 19)) + self.slider_5.SetMinSize((275, 19)) + if AM_SYNC_DISPLAY: + self.panel_9.SetMinSize((640, 240)) + self.slider_6.SetMinSize((300, 38)) + self.slider_7.SetMinSize((400, 38)) + # end wxGlade + + def __do_layout(self): + # begin wxGlade: MyFrame.__do_layout + sizer_1 = wx.BoxSizer(wx.VERTICAL) + grid_sizer_1 = wx.FlexGridSizer(11, 2, 0, 0) + sizer_7 = wx.BoxSizer(wx.HORIZONTAL) + sizer_5 = wx.BoxSizer(wx.HORIZONTAL) + sizer_4 = wx.BoxSizer(wx.HORIZONTAL) + sizer_3 = wx.BoxSizer(wx.HORIZONTAL) + sizer_6 = wx.BoxSizer(wx.VERTICAL) + sizer_2 = wx.BoxSizer(wx.HORIZONTAL) + grid_sizer_1.Add(self.panel_1, 1, wx.EXPAND, 0) + sizer_2.Add(self.button_1, 0, wx.ADJUST_MINSIZE, 0) + sizer_2.Add(self.button_2, 0, wx.ADJUST_MINSIZE, 0) + sizer_2.Add(self.button_3, 0, wx.ADJUST_MINSIZE, 0) + sizer_2.Add(self.button_4, 0, wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(sizer_2, 1, wx.EXPAND, 0) + grid_sizer_1.Add(self.button_5, 0, wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(self.slider_1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(self.button_6, 0, wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(self.slider_2, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0) + sizer_6.Add(self.panel_5, 1, wx.EXPAND, 0) + sizer_6.Add(self.label_1, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ADJUST_MINSIZE, 0) + sizer_6.Add(self.text_ctrl_1, 0, wx.ADJUST_MINSIZE, 0) + sizer_6.Add(self.panel_6, 1, wx.EXPAND, 0) + sizer_6.Add(self.panel_7, 1, wx.EXPAND, 0) + grid_sizer_1.Add(sizer_6, 1, wx.EXPAND, 0) + grid_sizer_1.Add(self.panel_2, 1, wx.EXPAND, 0) + grid_sizer_1.Add(self.button_7, 0, wx.ADJUST_MINSIZE, 0) + sizer_3.Add(self.slider_3, 0, wx.ADJUST_MINSIZE, 0) + sizer_3.Add(self.spin_ctrl_1, 0, wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(sizer_3, 1, wx.EXPAND, 0) + grid_sizer_1.Add(self.button_8, 0, wx.ADJUST_MINSIZE, 0) + sizer_4.Add(self.slider_4, 0, wx.ADJUST_MINSIZE, 0) + sizer_4.Add(self.slider_5, 0, wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(sizer_4, 1, wx.EXPAND, 0) + grid_sizer_1.Add(self.button_9, 0, wx.ADJUST_MINSIZE, 0) + sizer_5.Add(self.button_11, 0, wx.ADJUST_MINSIZE, 0) + sizer_5.Add(self.button_10, 0, wx.ADJUST_MINSIZE, 0) + sizer_5.Add(self.panel_3, 1, wx.EXPAND, 0) + sizer_5.Add(self.label_2, 0, wx.ADJUST_MINSIZE, 0) + sizer_5.Add(self.panel_4, 1, wx.EXPAND, 0) + grid_sizer_1.Add(sizer_5, 1, wx.EXPAND, 0) + grid_sizer_1.Add(self.panel_8, 1, wx.EXPAND, 0) + grid_sizer_1.Add(self.panel_9, 1, wx.EXPAND, 0) + grid_sizer_1.Add(self.label_3, 0, wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(self.slider_6, 0, wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(self.label_4, 0, wx.ALIGN_BOTTOM|wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(self.slider_7, 0, wx.ADJUST_MINSIZE, 0) + grid_sizer_1.Add(self.panel_10, 1, wx.EXPAND, 0) + sizer_7.Add(self.button_12, 0, wx.ADJUST_MINSIZE, 0) + sizer_7.Add(self.button_13, 0, wx.ADJUST_MINSIZE, 0) + sizer_7.Add(self.button_14, 0, wx.ADJUST_MINSIZE, 0) + sizer_7.Add(self.panel_11, 1, wx.EXPAND, 0) + sizer_7.Add(self.panel_12, 1, wx.EXPAND, 0) + grid_sizer_1.Add(sizer_7, 1, wx.EXPAND, 0) + sizer_1.Add(grid_sizer_1, 1, wx.EXPAND, 0) + self.SetAutoLayout(True) + self.SetSizer(sizer_1) + sizer_1.Fit(self) + sizer_1.SetSizeHints(self) + self.Layout() + # end wxGlade + + # Menu exit + def TimeToQuit(self, event): + self.fg.stop() + self.Close(True) + + # Powermate being turned + def on_rotate(self, event): + if self.active_button == 5: + self.slider_1.SetValue(self.slider_1.GetValue()+event.delta) + if self.slider_2.GetValue() > (self.slider_1.GetValue() - 200) : + self.slider_2.SetValue(self.slider_1.GetValue() - 200) + self.filter() + if self.active_button == 6: + self.slider_2.SetValue(self.slider_2.GetValue()+event.delta) + if self.slider_1.GetValue() < (self.slider_2.GetValue() + 200) : + self.slider_1.SetValue(self.slider_2.GetValue() + 200) + self.filter() + if self.active_button == 7: + new = max(0, min(6000, self.slider_3.GetValue() + event.delta)) + self.slider_3.SetValue(new) + self.frequency = (self.f_slider_scale * new) + self.f_slider_offset + self.spin_ctrl_1.SetValue(self.frequency) + if self.AM_mode == False: + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset)) + else: + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3)) + if self.button_12.GetValue(): + self.auto_antenna_tune() + if self.active_button == 8: + new = max(0, min(500, self.slider_4.GetValue() + event.delta)) + self.slider_4.SetValue(new) + self.scale.set_k(math.pow(10.,((self.slider_4.GetValue()-500.)/100.))) + if self.active_button == 9: + if self.PLAY_FROM_USRP == False: + if event.delta == -1: + self.src.seek(-1000000,gr.SEEK_CUR) + elif event.delta == 1: + self.src.seek(1000000,gr.SEEK_CUR) + + + # Powermate pressed to switch controlled function + def on_pmButton(self, event): + if event.value == 0: + if self.active_button == 5: + self.active_button = 6 + self.button_5.SetValue(False) + self.button_6.SetValue(True) + elif self.active_button == 6: + self.active_button = 7 + self.button_6.SetValue(False) + self.button_7.SetValue(True) + elif self.active_button == 7: + self.active_button = 8 + self.button_7.SetValue(False) + self.button_8.SetValue(True) + elif self.active_button == 8: + self.active_button = 9 + self.button_8.SetValue(False) + self.button_9.SetValue(True) + elif self.active_button == 9: + self.active_button = 5 + self.button_9.SetValue(False) + self.button_5.SetValue(True) + + # Clicking one PM control button turns the rest off + def on_button(self, event): + id = event.GetId() + if id == ID_BUTTON_5: + self.active_button = 5 + self.button_6.SetValue(False) + self.button_7.SetValue(False) + self.button_8.SetValue(False) + self.button_9.SetValue(False) + if id == ID_BUTTON_6: + self.active_button = 6 + self.button_5.SetValue(False) + self.button_7.SetValue(False) + self.button_8.SetValue(False) + self.button_9.SetValue(False) + if id == ID_BUTTON_7: + self.active_button = 7 + self.button_5.SetValue(False) + self.button_6.SetValue(False) + self.button_8.SetValue(False) + self.button_9.SetValue(False) + if id == ID_BUTTON_8: + self.active_button = 8 + self.button_5.SetValue(False) + self.button_6.SetValue(False) + self.button_7.SetValue(False) + self.button_9.SetValue(False) + if id == ID_BUTTON_9: + self.active_button = 9 + self.button_5.SetValue(False) + self.button_6.SetValue(False) + self.button_7.SetValue(False) + self.button_8.SetValue(False) + + # Make sure filter settings are legal + def set_filter(self, event): + slider = event.GetId() + slider1 = self.slider_1.GetValue() + slider2 = self.slider_2.GetValue() + if slider == ID_SLIDER_1: + if slider2 > (self.slider_1.GetValue() - 200) : + self.slider_2.SetValue(slider1 - 200) + elif slider == ID_SLIDER_2: + if slider1 < (self.slider_2.GetValue() + 200) : + self.slider_1.SetValue(slider2 + 200) + self.filter() + + # Calculate taps and apply + def filter(self): + audio_coeffs = gr.firdes.complex_band_pass ( + 1.0, # gain + self.af_sample_rate, # sample rate + self.slider_2.GetValue(), # low cutoff + self.slider_1.GetValue(), # high cutoff + 100, # transition + gr.firdes.WIN_HAMMING) # window + self.audio_filter.set_taps(audio_coeffs) + + def set_lsb(self, event): + self.AM_mode = False + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset)) + self.sel_sb.set_k(1) + self.sel_am.set_k(0) + self.slider_1.SetValue(0) + self.slider_2.SetValue(-3000) + self.filter() + + def set_usb(self, event): + self.AM_mode = False + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset)) + self.sel_sb.set_k(1) + self.sel_am.set_k(0) + self.slider_1.SetValue(3000) + self.slider_2.SetValue(0) + self.filter() + + def set_am(self, event): + self.AM_mode = True + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3)) + self.sel_sb.set_k(0) + self.sel_am.set_k(1) + self.slider_1.SetValue(12500) + self.slider_2.SetValue(2500) + self.filter() + + def set_cw(self, event): + self.AM_mode = False + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset)) + self.AM_mode = False + self.sel_sb.set_k(1) + self.sel_am.set_k(0) + self.slider_1.SetValue(-400) + self.slider_2.SetValue(-800) + self.filter() + + def set_volume(self, event): + self.scale.set_k(math.pow(10.,((self.slider_4.GetValue()-500.)/100.))) + + def set_pga(self,event): + if self.PLAY_FROM_USRP: + self.subdev.set_gain(self.slider_5.GetValue()) + + def slide_tune(self, event): + self.frequency = (self.f_slider_scale * self.slider_3.GetValue()) + self.f_slider_offset + if self.AM_mode == False: + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset)) + else: + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3)) + self.spin_ctrl_1.SetValue(self.frequency) + if self.button_12.GetValue(): + self.auto_antenna_tune() + + def spin_tune(self, event): + self.frequency = self.spin_ctrl_1.GetValue() + if self.AM_mode == False: + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset)) + else: + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3)) + self.slider_3.SetValue(int((self.frequency-self.f_slider_offset)/self.f_slider_scale)) + if self.button_12.GetValue(): + self.auto_antenna_tune() + + # Seek forwards in file + def fwd(self, event): + if self.PLAY_FROM_USRP == False: + self.src.seek(10000000,gr.SEEK_CUR) + + # Seek backwards in file + def rew(self, event): + if self.PLAY_FROM_USRP == False: + self.src.seek(-10000000,gr.SEEK_CUR) + + # Mouse over fft display - show frequency in tooltip + def Mouse(self,event): + if self.AM_mode: + fRel = ( event.GetX() - 330. ) / 14.266666 - 7.5 + else: + fRel = ( event.GetX() - 330. ) / 14.266666 + self.fft.win.SetToolTip(wx.ToolTip(eng_notation.num_to_str(self.frequency + (fRel*1e3)))) + + # Mouse clicked on fft display - change frequency + def Click(self,event): + fRel = ( event.GetX() - 330. ) / 14.266666 + if self.AM_mode == False: + self.frequency = self.frequency + (fRel*1e3) + else: + self.frequency = self.frequency + (fRel*1e3) - 7.5e3 + self.spin_ctrl_1.SetValue(int(self.frequency)) + self.slider_3.SetValue(int((self.frequency-self.f_slider_offset)/self.f_slider_scale)) + if self.AM_mode == False: + self.xlate.set_center_freq ( self.usrp_center - ( self.frequency - self.tune_offset )) + else: + self.xlate.set_center_freq( self.usrp_center - (self.frequency - self.tune_offset - 7.5e3)) + + # Set power of AM sync carrier + def am_carrier(self,event): + scale = math.pow(10,(self.slider_6.GetValue())/50.) + self.pll_carrier_scale.set_k(complex(scale,0)) + + # Reset AT data and start calibrate over + def AT_reset(self, event): + self.xdata = [] + self.ydata = [] + + # Save AT setting for a particular frequency + def AT_calibrate(self, event): + self.xdata.append(float(self.frequency)) + self.ydata.append(self.slider_7.GetValue()) + if len(self.xdata) > 1: + self.m = [] + self.b = [] + for i in range(0,len(self.xdata)-1): + self.m.append( (self.ydata[i+1] - self.ydata[i]) / (self.xdata[i+1] - self.xdata[i]) ) + self.b.append( self.ydata[i] - self.m[i] * self.xdata[i] ) + + # Lookup calibrated points and calculate interpolated antenna tune voltage. + # This is to automatically tune a narrowband loop antenna when the freq + # is changed, to keep signals peaked. + def auto_antenna_tune(self): + for i in range(0,len(self.xdata)-1): + if (self.frequency > self.xdata[i]) & (self.frequency < self.xdata[i+1]): + self.slider_7.SetValue(self.m[i]*self.frequency + self.b[i]) + self.antenna_tune(0) + + # Slider to set loop antenna capacitance + def antenna_tune(self, evt): + if self.PLAY_FROM_USRP: + self.src.write_aux_dac(0,3,self.slider_7.GetValue()) + + # Timer events - check for web commands + def OnUpdate(self): + cmds = os.listdir("/var/www/cgi-bin/commands/") + if cmds!=[]: + if cmds[0]=='chfreq': + fd=open("/var/www/cgi-bin/commands/chfreq","r") + new=fd.readline() + fd.close() + if new!='': + os.unlink("/var/www/cgi-bin/commands/chfreq") + if ( int(new) >= self.f_lo ) & ( int(new) <= self.f_hi ): + self.frequency = int(new) + self.slider_3.SetValue(( self.frequency - self.f_slider_offset) / self.f_slider_scale ) + self.spin_ctrl_1.SetValue(self.frequency) + if self.button_12.GetValue(): + self.auto_antenna_tune() + if self.AM_mode: + self.xlate.set_center_freq ( self.usrp_center - ( self.frequency - self.tune_offset - 7.5e3 )) + else: + self.xlate.set_center_freq ( self.usrp_center - ( self.frequency - self.tune_offset )) + + if cmds[0]=='chvolume': + fd=open("/var/www/cgi-bin/commands/chvolume","r") + new=fd.readline() + fd.close() + if new!='': + os.unlink("/var/www/cgi-bin/commands/chvolume") + if ( int(new) >= 0 ) & ( int(new) <= 500 ): + self.volume = int(new) + self.slider_4.SetValue(self.volume) + self.scale.set_k(math.pow(10.,((self.slider_4.GetValue()-500.)/100.))) + + else: # no new web commands, update state + fh = open("/var/www/cgi-bin/state/freq","w") + fh.write(str(int(self.frequency))+'\n') + fh.close() + fh = open("/var/www/cgi-bin/state/volume","w") + fh.write(str(self.slider_4.GetValue())+'\n') + fh.close() + + +# end of class MyFrame + +# wx.Timer to check for web updates +class UpdateTimer(wx.Timer): + def __init__(self, target, dur=1000): + wx.Timer.__init__(self) + self.target = target + self.Start(dur) + + def Notify(self): + """Called every timer interval""" + if self.target: + self.target.OnUpdate() + + +class MyApp(wx.App): + def OnInit(self): + frame = MyFrame(None, -1, "HF Explorer 2") + frame.Show(True) + self.SetTopWindow(frame) + return True + +app = MyApp(0) +app.MainLoop() + diff --git a/gnuradio-examples/python/apps/hf_explorer/hfx_help b/gnuradio-examples/python/apps/hf_explorer/hfx_help new file mode 100644 index 000000000..9a52dd2bb --- /dev/null +++ b/gnuradio-examples/python/apps/hf_explorer/hfx_help @@ -0,0 +1,180 @@ + + HF Explorer Help + + ----------------------------------------------------------------------- + + Command Line Switches: + + -c DDC center frequency, set band. + -c 7.2e6 or -c 7.2M for 40 meter ham band. + Default is 3.9e6 80 meter ham band. + Example: + + hfx.py -c 9500k + + starts up in the 31 meter band. + + -a Audio output file. Output file for 32kHz two channel + signed word audio. Two channels are used for + independent sideband. This file can be converted + to a wav file with sox. Example: + + sox -c 2 -r 3200 file.sw file.wav + + sox needs the .sw extension to indicate file type. + If not specified no audio file is created. + + -r Radio output file. File to write RF data to for later + demodulation. Records the entire band to disk, width + determined by sample rate/decimation. Be sure to + note the decimation and center freq for later use! + Example: + + hfx.py -c 900e3 -d 80 -r rf_data_AM-c900e3-d80 + + writes a pre-demod rf file centered on 900kHz with a + bandwidth of 800kHz (That's 80 AM stations!). The + center and decimation could be put in the filename for + proper use later. + If not specified no rf data file is created. + At default 250 decimation disk usage is about + 8Gb / hour. + + -i Radio input file. Use to play back a previously + recorded rf data file. Example: + + hfx.py -c 900e3 -d 80 -i rf_data_AM-c900e3-d80 + + plays back the previously recorded band, no + usrp hardware needed. Tune about the 800kHz wide band. + When playing a recorded file, time controls + fast-forward and rewind are available. + + -d Decimation. Sets sample rate and bandwidth. + This is the factor that the usrp sample rate, 64e6, + is divided by. Default is 250 for 256kHz bandwidth + which is enough to record a ham band without + eating up disk space too fast. The 64e6 sample + rate limits the upper practical frequency to 32MHz. + The Basic RX transformer limits the lower frequency + to about 200kHz. + + + Powermate Knob: + + A Powermate knob is recommended but not necessary. If a knob + is used, it is in one of 3 or 4 modes controlling frequency, + volume, filter and (if playing a recorded file) time. + Pushing the knob switches mode and the buttons on the HFX panel + change to show which mode is in effect. Then just turn the knob + to set frequency, volume, filter or go fast forward or rewind. + + + Bandswitch: + + Across the top are a set of predefined bands and buttons + to rapidly switch to the center of that band. To change a band, + type the frequency in Hz into the box under "Center Frequency", + then press "Set" on the left, then the button you want to + program. From then on (untill the program is exited) pushing + that button takes you to that band. To make a band button + permenant edit the hfx.py script with whatever frequency you + want assigned to what button. + + + Frequency: + + There are 6 ways to set the frequency. + 1) Move the slider with the mouse + 2) Use the Spin Control up/down arrows (very fine 1Hz control) + 3) Type the frequency in Hz into the Spin Control + 4) Turn the Powermate knob + 5) Web control. + 6) Clicking on the FFT display to set demod center. This is very + convenient for tuning +-15kHz when you see a signal on the + display. If in Lower Sideband, clicking just to the right of + a signal will tune to it immediately. Clicking several times + on the far right right or left of the display will rapidly + tune up or down the band. + + + Volume: + + Move the volume slider with the mouse, or push the Powermate knob + untill the volume button is active, or click on the volume button, + then turn the knob. Volume can also be set by web control if web + control is setup and enabled. + + + Filter: + + Similar to volume, switches in any of 30 audio filters from 600 + to 3600Hz in Sideband or up to 5kHz in AM. + + + Mode: + + Demodulation modes are chosen by clicking on the buttons for + Lower Sideband, Upper Sideband, or AM. + + + PGA: + + PGA slider sets the rf gain in the Analog-to-Digital converter + before digitizing. 0 to 20db gain easily shows up on the FFT + display. + + + Time: + + When playing back a recorded RF data file, you can enjoy the + freedom of rewinding or fast-forwarding. Replay a weak signal + or skip through annoying AM commercials. + + + Antennas and Preamps: + + The USRP Basic RX board is not sensitive enough for anything but + the strongest signals. In my experience about 40 db of small + signal gain is required to make the HFX as sensitive as other + receivers. Some working amplifiers are the Ramsey PR-2 with 20db + gain, fairly low noise and more bandwidth than we can use here. + Also the amp modules from Advanced Receiver Research are nice. + I use an ARR 7-7.4MHz GaAsFET 29db amp with .5db noise at the + apex of a 40 meter dipole with excellent results. Another + amp I like is a Minicircuits ZHL-32A 29db amp but they are + expensive and hard to find. Also it may help to use some filters + to keep strong local signals from the ADC, or limit rf input + to the band of interest, etc. + Resonant outdoor antennas, like a dipole, in a low-noise (away + from consumer electronics) environment are nice. Long random wires + with a tuner work. I like a small indoor tuned loop made from 10ft + of 1/4" copper tube, a 365pf tuning cap and a pickup loop connected + to rg-58. + + + Web Control: + + To control your radio remotely, ensure you have a web server + (Apache, etc) working and a compatible directory structure in + place. Directories /var/www/cgi-bin/commands and + /var/www/cgi-bin/state must already exist. You will need a + home page with forms and a set of scripts to put commands in + and put the current state on the home page. email me for further + help. Setting WEB_CONTROL to True in hfx.py turns on the timers + that check for commands and update the state. + + + IF Output: + + There is a provision for outputting un-demodulated complex + through the audio out in stereo for use with Digital Radio + Mondial (DRM) or using a seperate demodulation program like + SDRadio (by I2PHD). + Set IF_OUTPUT to True in weaver_isb_am1_usrp4.py. + + + --Good luck and happy LW/MW/SW Exploring. + Chuck + chuckek@musicriver.homeunix.com + |