gqrx/gqrx.py
2011-04-22 22:01:25 +02:00

994 lines
37 KiB
Python
Executable File

#!/usr/bin/env python
#
# Simple multi mode receiver implemented in GNU Radio with Qt GUI
# This program is based on usrp_display.py from GNU Radio.
#
# Copyright 2009 Free Software Foundation, Inc.
# Copyright 2010 Alexandru Csete
#
# GNU Radio and gqrx are free software; you can redistribute and/or modify
# them under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# GNU Radio and gqrx are 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., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#
from gnuradio import gr, gru, audio, blks2
from gnuradio import usrp
from gnuradio import eng_notation
from gnuradio.gr import firdes
from gnuradio.eng_option import eng_option
from optparse import OptionParser
from datetime import datetime
import sys
try:
from gnuradio.qtgui import qtgui
from PyQt4 import QtGui, QtCore
import sip
except ImportError:
print "Please install gr-qtgui."
sys.exit(1)
try:
from gqrx_qtgui import Ui_MainWindow
except ImportError:
print "Error: could not find gwrx_qtgui.py:"
print "\t\"pyuic4 gqrx_qtgui.ui -o gqrx_qtgui.py\""
sys.exit(1)
# USRP bandwidth supported by the receiver
# 250k must be there and the others must be an integer multiple of 250k
bwtable = [250000, 500000, 1000000, 2000000, 4000000]
bwstr = ["250 kHz", "500 kHz", "1 MHz", "2 MHz", "4 MHz"]
# file format used for recordings
rec_format = "%Y.%m.%d-%H.%M.%S"
# Qt interface
class main_window(QtGui.QMainWindow):
def __init__(self, snk, fg, parent=None):
QtGui.QWidget.__init__(self, parent)
self.gui = Ui_MainWindow()
self.gui.setupUi(self)
self.fg = fg
# Add the qtsnk widgets to the layout box
self.gui.sinkLayout.addWidget(snk)
# set up range for RF gain spin box
g = self.fg.subdev.gain_range()
self.gui.gainSpin.setRange(g[0], g[1])
self.gui.gainSpin.setValue(self.fg.options.gain)
# Populate the bandwidth combo
for bwlabel in bwstr:
self.gui.bandwidthCombo.addItem(bwlabel, None)
# Populate the filter shape combo box and select "Normal"
self.gui.filterShapeCombo.addItem("Soft", None)
self.gui.filterShapeCombo.addItem("Normal", None)
self.gui.filterShapeCombo.addItem("Sharp", None)
self.gui.filterShapeCombo.setCurrentIndex(1)
# Mode selector combo
self.gui.modeCombo.addItem("AM", None)
self.gui.modeCombo.addItem("FM-N", None)
self.gui.modeCombo.addItem("FM-W", None)
self.gui.modeCombo.addItem("LSB", None)
self.gui.modeCombo.addItem("USB", None)
self.gui.modeCombo.addItem("CW-L", None)
self.gui.modeCombo.addItem("CW-U", None)
self.gui.modeCombo.setCurrentIndex(1)
# AGC selector combo
self.gui.agcCombo.addItem("Fast", None)
self.gui.agcCombo.addItem("Medium", None)
self.gui.agcCombo.addItem("Slow", None)
self.gui.agcCombo.addItem("Off", None)
self.gui.agcCombo.setCurrentIndex(1)
### Disable functions that have not been implemented yet
self.gui.recSpectrumButton.setEnabled(False)
self.gui.agcCombo.setEnabled(False) # There is an AGC block but with fixed values
# Connect signals
# Frequency controls
self.connect(self.gui.freqUpBut1, QtCore.SIGNAL("clicked()"),
self.freqUpBut1Clicked)
self.connect(self.gui.freqUpBut2, QtCore.SIGNAL("clicked()"),
self.freqUpBut2Clicked)
self.connect(self.gui.freqDownBut1, QtCore.SIGNAL("clicked()"),
self.freqDownBut1Clicked)
self.connect(self.gui.freqDownBut2, QtCore.SIGNAL("clicked()"),
self.freqDownBut2Clicked)
self.connect(self.gui.frequencyEdit, QtCore.SIGNAL("editingFinished()"),
self.frequencyEditText)
# Bandwidth selector
self.connect(self.gui.bandwidthCombo, QtCore.SIGNAL("activated(int)"),
self.bandwidth_changed)
# RF gain
self.connect(self.gui.gainSpin, QtCore.SIGNAL("valueChanged(int)"),
self.gainChanged)
# Pause button
self.connect(self.gui.pauseButton, QtCore.SIGNAL("clicked()"),
self.pauseFg)
# Filter controls
self.connect(self.gui.tuningSlider, QtCore.SIGNAL("valueChanged(int)"),
self.tuning_changed)
self.connect(self.gui.filterWidthSlider, QtCore.SIGNAL("valueChanged(int)"),
self.filter_width_changed)
self.connect(self.gui.filterCenterSlider, QtCore.SIGNAL("valueChanged(int)"),
self.filter_center_changed)
self.connect(self.gui.filterShapeCombo, QtCore.SIGNAL("activated(int)"),
self.filter_shape_changed)
# Mode change combo
self.connect(self.gui.modeCombo, QtCore.SIGNAL("activated(int)"),
self.mode_changed)
# AGC selector combo
self.connect(self.gui.agcCombo, QtCore.SIGNAL("activated(int)"),
self.agc_changed)
# Squelch threshold
self.connect(self.gui.sqlSlider, QtCore.SIGNAL("valueChanged(int)"),
self.squelch_changed)
# Audio gain, recording and playback
self.connect(self.gui.volSlider, QtCore.SIGNAL("valueChanged(int)"),
self.af_gain_changed)
self.connect(self.gui.recAudioButton, QtCore.SIGNAL("toggled(bool)"),
self.af_rec_toggled)
self.connect(self.gui.playAudioButton, QtCore.SIGNAL("toggled(bool)"),
self.af_play_toggled)
# misc
self.connect(self.gui.actionSaveData, QtCore.SIGNAL("activated()"),
self.saveData)
self.gui.actionSaveData.setShortcut(QtGui.QKeySequence.Save)
# Functions to set the values in the GUI
def set_frequency(self, freq):
self.freq = freq
sfreq = eng_notation.num_to_str(self.freq)
self.gui.frequencyEdit.setText(QtCore.QString("%1").arg(sfreq))
def set_bandwidth(self, bw):
"Update bandwidth selector combo"
self.bw = bw
#sbw = eng_notation.num_to_str(self.bw)
#self.gui.bandwidthEdit.setText(QtCore.QString("%1").arg(sbw))
if bw == 250000:
self.gui.bandwidthCombo.setCurrentIndex(0)
elif bw == 500000:
self.gui.bandwidthCombo.setCurrentIndex(1)
elif bw == 1000000:
self.gui.bandwidthCombo.setCurrentIndex(2)
elif bw == 2000000:
self.gui.bandwidthCombo.setCurrentIndex(3)
elif bw == 4000000:
self.gui.bandwidthCombo.setCurrentIndex(4)
else:
print "Invalid bandwidth for bandwidthCombo: ", bw
def set_filter_width_slider_value(self, width):
"""
This function will update the state of the filter width slider.
This will trigger the valueChanged() signal, which in turn will
update the filter width of the receiver.
"""
self.fw = width
self.gui.filterWidthSlider.setValue(width)
def set_filter_center_slider_value(self, offset):
"""
This function will update the state of the filter center slider.
This will trigger the valueChanged() signal, which in turn will
update the filter width of the receiver.
"""
self.fc = offset
self.gui.filterCenterSlider.setValue(offset)
def set_filter_width_range(self, lower=1000, upper=15000, step=100):
"""
Set new lower and upper limit for the filter width widgets.
The function updates both the range of the slider and the spin box.
Standard ranges and step sizes:
FM-W: 50-200 kHz with 1 kHz step
AM and FM-N: 1-15 kHz with 100 Hz step
LSB and USB: 1-5 kHz with 50 Hz step
CW: 0.1-3 kHz with 10 Hz step
"""
if lower > upper:
print "Invalid filter range: ",lower," > ",upper
return
# slider
self.gui.filterWidthSlider.setRange(lower, upper)
self.gui.filterWidthSlider.setSingleStep(step)
self.gui.filterWidthSlider.setPageStep(10*step)
# spin box
self.gui.filterWidthSpin.setRange(lower, upper)
self.gui.filterWidthSpin.setSingleStep(step)
def set_tuning_range(self, rng):
"""
Set new tuning range.
This function will update the limits of the tuning slider and spin box
to +/- rng
"""
self.gui.tuningSlider.setRange(-rng, rng)
self.gui.tuningSpin.setRange(-rng, rng)
# TODO: missing implementations, but do we really need them?
# Functions called when signals are triggered in the GUI
def pauseFg(self):
if(self.gui.pauseButton.text() == "Pause"):
self.fg.stop()
self.fg.wait()
self.gui.pauseButton.setText("Unpause")
else:
self.fg.start()
self.gui.pauseButton.setText("Pause")
def frequencyEditText(self):
"Function called when a new frqeuency is entered into the text field."
try:
freq = eng_notation.str_to_num(self.gui.frequencyEdit.text().toAscii())
self.fg.set_frequency(freq)
self.freq = freq
except RuntimeError:
pass
def freqUpBut1Clicked(self):
"""
Function called when the > button is clicked.
It increases the USRP frequency by 1/10 of the bandwidth.
"""
self.freq += int(self.bw/10)
self.fg.set_frequency(self.freq)
def freqUpBut2Clicked(self):
"Function called when the >> button is clicked."
self.freq += self.bw
self.fg.set_frequency(self.freq)
def freqDownBut1Clicked(self):
"""
Function called when the < button is clicked.
It increases the USRP frequency by 1/10 of the bandwidth.
"""
self.freq -= int(self.bw/10)
self.fg.set_frequency(self.freq)
def freqDownBut2Clicked(self):
"Function called when the << button is clicked"
self.freq -= self.bw
self.fg.set_frequency(self.freq)
def gainChanged(self, gain):
self.gain = gain
self.fg.set_gain(gain)
def bandwidth_changed(self, bw):
"New bandwidth selected."
if bw < 5:
nbw = bwtable[bw]
else:
print "Invalid bandwidth index: ", bw
nbw = self.bw
if nbw != self.bw:
self.bw = nbw
#print "New bandwidth: ", self.bw
self.fg.set_bandwidth(self.bw)
def tuning_changed(self, value):
"Tuning value changed"
self.fg.set_xlate_offset(value)
def filter_width_changed(self, value):
"Filter width changed."
self.fw = value
self.fg.set_filter_width(value)
def filter_center_changed(self, value):
"Filter center changed."
self.fc = value
#self.fg.set_filter_offset(value)
self.fg.set_filter_offset(-value) # opposite when complex BPF is in the xlating_filter
def filter_shape_changed(self, index):
"Filter shape changed."
self.fs = index
self.fg.set_filter_shape(index)
def mode_changed(self, mode):
"New mode selected."
self.mode = mode
self.fg.set_mode(mode)
def agc_changed(self, agc):
"New AGC selected."
self.agc = agc
self.fg.set_agc(agc)
def squelch_changed(self, sql):
"New squelch threshold set."
self.sql = sql
self.fg.set_squelch(self.sql)
def af_gain_changed(self, vol):
"New AF gain value set."
self.afg = vol
self.fg.set_af_gain(vol/10.0) # slider is int 0-50, real value 0.0-5.0
def af_rec_toggled(self, checked):
"""
The REC button has been toggled. If "checked = True" then the REC button
is in (checked) otherwise it is out (not checked)
"""
if checked == True:
self.fg.start_audio_recording()
else:
self.fg.stop_audio_recording()
def af_play_toggled(self, checked):
"""
The Play button has been toggled. If "checked = True" then the Play button
is in (checked) otherwise it is out (not checked)
"""
if checked == True:
if self.fg.start_audio_playback():
# there was an error
self.gui.playAudioButton.setChecked(False)
else:
self.fg.stop_audio_playback()
def saveData(self):
fileName = QtGui.QFileDialog.getSaveFileName(self, "Save data to file", ".");
if(len(fileName)):
self.fg.save_to_file(str(fileName))
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 my_top_block(gr.top_block):
def __init__(self):
gr.top_block.__init__(self)
# Variables
self._current_mode = 1 # initial mode is FMN
self._xlate_offset = 0 # tuning offset of the xlating filter
self._filter_offset = 0
self._filter_low = -5000
self._filter_high = 5000
self._filter_trans = 2000
self._agc_decay = 50e-6
#self._if_rate = 250000 # sample rate at the input of demodulators
self._demod_rate = 50000 # sample rate at the input of demodulators (except WFM)
self._audio_rate = 44100 # Sample rate of sound card
self._cur_audio_rec = None # Current audio recording
self._prev_audio_rec = None # Previous audio recording (needed for speedy playback)
parser = OptionParser(option_class=eng_option)
parser.add_option("-w", "--which", type="int", default=0,
help="select which USRP (0, 1, ...) default is %default",
metavar="NUM")
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)")
parser.add_option("-A", "--antenna", default=None,
help="select Rx Antenna (only on WBX and RFX boards)")
parser.add_option("-W", "--bw", type="int", default=250e3,
help="set bandwidth of receiver [default=%default]")
parser.add_option("-f", "--freq", type="eng_float", default=None,
help="set frequency to FREQ", metavar="FREQ")
parser.add_option("-g", "--gain", type="eng_float", default=None,
help="set gain in dB [default is midpoint]")
parser.add_option("-a", "--ar", type="int", default=44100,
help="set sample rate for soundcard [default=%default]")
parser.add_option("-O", "--audio-output", type="string", default="",
help="audio device name, e.g. plughw:0,0")
parser.add_option("", "--fft-size", type="int", default=2048,
help="Set FFT frame size, [default=%default]");
(options, args) = parser.parse_args()
if len(args) != 0:
parser.print_help()
sys.exit(1)
self.options = options
self.show_debug_info = True
# Call this before creating the Qt sink
self.qapp = QtGui.QApplication(sys.argv)
self._fftsize = options.fft_size
self.u = usrp.source_c(which=options.which)
self._adc_rate = self.u.converter_rate()
if options.bw in bwtable:
self._bandwidth = options.bw
else:
self._bandwidth = bwtable[0]
self._decim = int(self._adc_rate / self._bandwidth)
self.u.set_decim_rate(self._decim)
if options.rx_subdev_spec is None:
options.rx_subdev_spec = pick_subdevice(self.u)
self._rx_subdev_spec = options.rx_subdev_spec
self.u.set_mux(usrp.determine_rx_mux_value(self.u, self._rx_subdev_spec))
# determine the daughterboard subdevice we're using
self.subdev = usrp.selected_subdev(self.u, self._rx_subdev_spec)
self._gain_range = self.subdev.gain_range()
if options.gain is None:
# if no gain was specified, use the mid-point in dB
g = self._gain_range
options.gain = float(g[0]+g[1])/2
self.set_gain(options.gain)
if options.freq is None:
# if no frequency was specified, use the mid-point of the subdev
f = self.subdev.freq_range()
options.freq = float(f[0]+f[1])/2
self.set_frequency(options.freq)
# Select antenna connector
if options.antenna is not None:
print "Selecting antenna %s" % (options.antenna,)
self.subdev.select_rx_antenna(options.antenna)
# set soundcard sample rate
self._audio_rate = options.ar
# Create FFT scope and waterfall sinks
self.snk = qtgui.sink_c(self._fftsize, firdes.WIN_BLACKMAN_hARRIS,
self._freq, self._bandwidth,
"USRP Display",
True, False, False, False)
# frequency xlating filter used for tuning and decimation
# to bring "demo rate" down to 50 ksps regardless of USRP decimation (250k for FM-W)
taps = firdes.complex_band_pass(1, self._bandwidth,
self._filter_low,
self._filter_high,
self._filter_trans,
firdes.WIN_HAMMING, 6.76)
self.xlf = gr.freq_xlating_fir_filter_ccc(5, # decimation 250k -> 50k
taps,
0, # center offset
self._bandwidth)
# Squelch (TODO: what's a good range for level? Now 0..100)
# alpha determines the "hang time" but SNR also has influence on that
self.sql = gr.simple_squelch_cc(threshold_db=-50.0, alpha=0.0003)
# AGC
self.agc = gr.agc2_cc(attack_rate=0.1,
decay_rate=self._agc_decay,
reference=0.5,
gain=1.0,
max_gain=0.6)
# AM demodulator
self.demod_am = blks2.am_demod_cf(channel_rate=self._demod_rate,
audio_decim=1,
audio_pass=5000,
audio_stop=5500)
# Narrow FM demodulator
self.demod_fmn = blks2.nbfm_rx(audio_rate=self._demod_rate,
quad_rate=self._demod_rate,
tau=75e-6, max_dev=5e3)
# Wide FM demodulator
self.demod_fmw = blks2.wfm_rcv(quad_rate=250000,
audio_decimation=5)
# SSB/CW demodulator
self.demod_ssb = gr.complex_to_real(1)
# Select FM-N as default demodulator
self.demod = self.demod_fmn
# audio resampler 50k -> audio_rate (44.1k or 48k)
interp = int(gru.lcm(self._demod_rate, self._audio_rate) / self._demod_rate)
decim = int(gru.lcm(self._demod_rate, self._audio_rate) / self._audio_rate)
self.audio_rr = blks2.rational_resampler_fff(interpolation=interp,
decimation=decim,
taps=None,
fractional_bw=None)
# audio gain and sink
self.audio_gain = gr.multiply_const_ff(1.0)
self.audio_sink = audio.sink(self._audio_rate, options.audio_output, True)
# Audio recorder block
# Create using dummy filename then close it right away
self.audio_recorder = gr.wavfile_sink(filename="/dev/null",
n_channels=1,
sample_rate=self._audio_rate,
bits_per_sample=16)
self.audio_recorder.close()
# NULL sink required during audio playback
self.audio_nullsink = gr.null_sink(gr.sizeof_float)
# audio_player is created when playback is started; however, we need
# to declare it because (audio_player == None) is used to determine
# whether a playback is ongoing or not (see audio_packback and recordin functions)
self.audio_player = None
# Connect the flow graph
self.connect(self.u, self.snk)
self.connect(self.u, self.xlf, self.sql, self.demod,
self.audio_rr, self.audio_gain, self.audio_sink)
# Get the reference pointer to the SpectrumDisplayForm QWidget
# Wrap the pointer as a PyQt SIP object
# This can now be manipulated as a PyQt4.QtGui.QWidget
self.pysink = sip.wrapinstance(self.snk.pyqwidget(), QtGui.QWidget)
self.main_win = main_window(self.pysink, self)
self.main_win.set_frequency(self._freq)
self.main_win.set_bandwidth(self._bandwidth)
# Window title string
if self._rx_subdev_spec[0] == 0:
self.main_win.setWindowTitle("GQRX: " + self.subdev.name() + " on side A")
else:
self.main_win.setWindowTitle("GQRX: " + self.subdev.name() + " on side B")
self.main_win.show()
def save_to_file(self, name):
# Pause the flow graph
self.stop()
self.wait()
# Add file sink to save data
self.file_sink = gr.file_sink(gr.sizeof_gr_complex, name)
self.connect(self.amp, self.file_sink)
# Restart flow graph
self.start()
def set_gain(self, gain):
"Set USRP gain"
self._gain = gain
self.subdev.set_gain(self._gain)
def set_frequency(self, freq):
"""
Tune USRP to new frequency.
If tuning is successful, update the frequency entry widget and the spectrum display.
"""
self._freq = freq
r = self.u.tune(0, self.subdev, self._freq)
if r:
print "New freq BB:", r.baseband_freq, " DDC:", r.dxc_freq
try:
self.main_win.set_frequency(self._freq)
self.snk.set_frequency_range(self._freq, self._bandwidth)
except:
pass
else:
print "Failed to set frequency to ", freq
def set_bandwidth(self, bw):
"Set USRP bandwidth"
if bw not in bwtable:
print "Invalid bandwidth: ", bw
return
self.lock()
self._bandwidth = bw
print "New bandwidth: ", self._bandwidth
# set new decimation for USRP
self._decim = int(self._adc_rate / self._bandwidth)
print " New USRP decimation: ", self._decim
self.u.set_decim_rate(self._decim)
# finally, update the tuning slider and spinbox
self.main_win.set_tuning_range(int(self._bandwidth/2))
# offset could be out of range when switching to a lower bandwidth
# set_tuning_range() has already limited it, we just need to grab the new value
self._xlate_offset = self.main_win.gui.tuningSlider.value()
# disconnect filter
self.disconnect(self.u, self.xlf, self.sql)
# reconfigure frequency xlating filter
# FIXME: I'm not exactly sure about this...
del self.xlf
taps = firdes.complex_band_pass(1, self._bandwidth,
self._filter_low,
self._filter_high,
self._filter_trans,
firdes.WIN_HAMMING, 6.76)
# for FMW we deen 250ksps, all other modes 50ksps
if self._current_mode == 2:
xlf_decim = int(self._bandwidth/250000)
else:
xlf_decim = int(self._bandwidth/50000)
self.xlf = gr.freq_xlating_fir_filter_ccc(xlf_decim,
taps,
self._xlate_offset, bw)
print " New filter decimation: ", xlf_decim
# reconnect new filter
self.connect(self.u, self.xlf, self.sql)
try:
self.snk.set_frequency_range(self._freq, self._bandwidth)
except:
pass
self.unlock()
def set_xlate_offset(self, offset):
"Set xlating filter offset"
self._xlate_offset = -offset
self.xlf.set_center_freq(self._xlate_offset)
def set_filter_width(self, width):
"Set new filter bandpass filter width"
self._filter_low = self._filter_offset - int(width/2)
self._filter_high = self._filter_offset + int(width/2)
self.xlf.set_taps(firdes.complex_band_pass(1, self._bandwidth,
self._filter_low,
self._filter_high,
self._filter_trans,
firdes.WIN_HAMMING, 6.76))
def set_filter_offset(self, offset):
"Set new offset for bandpass filter"
self._filter_offset = offset
width = self._filter_high - self._filter_low
self._filter_low = self._filter_offset - int(width/2)
self._filter_high = self._filter_offset + int(width/2)
# we need to update the filter shape as we go
self.set_filter_shape(self.main_win.gui.filterShapeCombo.currentIndex())
self.xlf.set_taps(firdes.complex_band_pass(1, self._bandwidth,
self._filter_low,
self._filter_high,
self._filter_trans,
firdes.WIN_HAMMING, 6.76))
def set_filter_shape(self, index):
"""
Set the filter shape to soft, normal or sharp.
The filter shape is determined by the transition width.
Soft: 40% of the filter width
Normal: 25% of the filter width
Sharp: 10% of the filter width
"""
width = self._filter_high - self._filter_low
if index == 0:
self._filter_trans = int(0.4*width) # soft, 20% of filter width
elif index == 1:
self._filter_trans = int(0.25*width) # normal, 10% of filter width
elif index == 2:
self._filter_trans = int(0.1*width) # sharp, 5% of filter width
else:
raise RuntimeError("Unknown filter shape")
# lower than this will probably not work
if self._filter_trans < 500:
self._filter_trans = 500
self.xlf.set_taps(firdes.complex_band_pass(1, self._bandwidth,
self._filter_low,
self._filter_high,
self._filter_trans,
firdes.WIN_HAMMING, 6.76))
def set_mode(self, mode):
"""
Set new operating mode. The parameter has a numeric value corresponding
to the modes indicated below.
Mode change consists of the following steps:
1. Stop the flow graph
2. Disconnect the squelch, demodulator, AGC, BPF and audio resampler
3. Set new demodulator
4. Reconnect the block
5. ensure that filter parameters are consistent with new mode
6. In case of transition to/from WFM, set new filter decimation
7. Restart the flow graph
The exact blocks that are (dis)connected depend on the previous and the
new operating mode since AGC is not used in FM modes.
"""
if mode == self._current_mode:
return
if ((mode == 2) or (self._current_mode == 2)):
need_filter_reconf = True
else:
need_filter_reconf = False
self.lock()
# disconnect the blocks that need to be reconfigured
if self._current_mode in [1,2]:
# in FM mode we have neither AGC nor SSB downsampler
self.disconnect(self.sql, self.demod, self.audio_rr)
elif self._current_mode in [0,3,4,5,6]:
# in AM, SSB and CW mode we have AGC
self.disconnect(self.sql, self.agc, self.demod, self.audio_rr)
else:
raise RuntimeError("Invalid state self._current_mode = " + self._current_mode)
if mode == 0:
self.demod = self.demod_am
self._fm_active = False
self.connect(self.sql, self.agc, self.demod, self.audio_rr)
self.main_win.set_filter_width_range(1000, 15000, 100)
self.main_win.set_filter_center_slider_value(0)
self.main_win.set_filter_width_slider_value(8000)
print "New mode: AM"
elif mode == 1:
self.demod = self.demod_fmn
self.connect(self.sql, self.demod, self.audio_rr)
self.main_win.set_filter_width_range(1000, 15000, 100)
self.main_win.set_filter_center_slider_value(0)
self.main_win.set_filter_width_slider_value(10000)
print "New mode: FM-N"
elif mode == 2:
self.demod = self.demod_fmw
self.connect(self.sql, self.demod, self.audio_rr)
self.main_win.set_filter_width_range(50000, 200000, 1000)
self.main_win.set_filter_center_slider_value(0)
self.main_win.set_filter_width_slider_value(160000)
print "New mode: FM-W"
elif mode == 3:
#self.disconnect(self.agc, self.demod, self.resampler)
self.demod = self.demod_ssb
self.connect(self.sql, self.agc, self.demod, self.audio_rr)
self.main_win.set_filter_width_range(1000, 5000, 50)
self.main_win.set_filter_center_slider_value(-1500)
self.main_win.set_filter_width_slider_value(2400)
print "New mode: LSB"
elif mode == 4:
#self.disconnect(self.agc, self.demod, self.resampler)
self.demod = self.demod_ssb
self.connect(self.sql, self.agc, self.demod, self.audio_rr)
self.main_win.set_filter_width_range(1000, 5000, 50)
self.main_win.set_filter_center_slider_value(1500)
self.main_win.set_filter_width_slider_value(2400)
print "New mode: USB"
elif mode == 5:
#self.disconnect(self.agc, self.demod, self.resampler)
self.demod = self.demod_ssb
self.connect(self.sql, self.agc, self.demod, self.audio_rr)
self.main_win.set_filter_width_range(100, 3000, 10)
self.main_win.set_filter_center_slider_value(-700)
self.main_win.set_filter_width_slider_value(1400)
print "New mode: CW-L"
elif mode == 6:
#self.disconnect(self.agc, self.demod, self.resampler)
self.demod = self.demod_ssb
self.connect(self.sql, self.agc, self.demod, self.audio_rr)
self.main_win.set_filter_width_range(100, 3000, 10)
self.main_win.set_filter_center_slider_value(700)
self.main_win.set_filter_width_slider_value(1400)
print "New mode: CW-U"
else:
raise RuntimeError("Invalid mode requested: " + mode)
# store the new mode
self._current_mode = mode
# if a tansition to/from FM-W has taken place we need to
# reconfigure the filter decimation. We can do that by
# simpl calling set_bandwidth(current_bandwidth)
if need_filter_reconf:
self.set_bandwidth(self._bandwidth)
# Restart the flow graph
self.unlock()
def set_agc(self, agc):
"""Set new AGC value"""
if agc == 0:
print "New AGC: Fast"
elif agc == 1:
print "New AGC: Medium (not implemented)"
elif agc == 2:
print "New AGC: Slow (not implemented)"
elif agc == 3:
print "New AGC: Off (not implemented)"
else:
print "Invalid AGC: ", agc
def set_squelch(self, sql):
"""Set new squelch threshold (dB)"""
self.sql.set_threshold(sql)
def set_af_gain(self, afg):
"""Set new AF gain"""
print "New AF Gain: ", afg
self.audio_gain.set_k(afg)
def start_audio_recording(self):
"""
This function connects the wave file sink to the output of AF gain and
starts recording the audio to a WAV file. If there already is an audio
recording ongoing, the function will do nothing.
"""
if self._cur_audio_rec != None:
print "ERROR: Already recording audio: ", self._cur_audio_rec
return
# Generate new file name based on date and time
self._cur_audio_rec = datetime.now().strftime(rec_format) + ".wav"
print "Start audio recording: ", self._cur_audio_rec
# Update GUI label
self.main_win.gui.audioRecLabel.setText(self._cur_audio_rec)
# open wav file and connect audio recorder
self.lock()
self.audio_recorder.open(self._cur_audio_rec) # FIXME: correct order?
self.connect(self.audio_gain, self.audio_recorder)
self.unlock()
def stop_audio_recording(self):
"""
This function stops the ongoing audio recording (if any) and disconnects
the wave file sink from the AF gain.
"""
if self._cur_audio_rec == None:
print "ERROR: There is no audio recording to stop"
return
# stop the flowgraph while we disconnect the audio recorder
self.lock()
self.disconnect(self.audio_gain, self.audio_recorder) # FIXME: correct order?
self.audio_recorder.close()
self._prev_audio_rec = self._cur_audio_rec
self._cur_audio_rec = None
print "Audio recording stopped: ", self._prev_audio_rec
# Restart the flowgraph
self.unlock()
def start_audio_playback(self):
"""
This function starts playback of the latest audio recording. During the playback,
the receiver flowgraph is routed to a null sink.
The function does nothing if an audio recording is currently ongoing or there are
no audio recordings to play.
The audio playback will be repeating until switched off.
"""
if self._cur_audio_rec != None:
print "ERROR: Can not play while recording: ", self._cur_audio_rec
return 1
if self._prev_audio_rec == None:
print "No audio recording to play"
return 1
if self.audio_player != None:
print "ERROR: Already playing audio"
return 1
# stop the flowgraph while we re-wire
self.lock()
self.disconnect(self.audio_rr, self.audio_gain)
self.connect(self.audio_rr, self.audio_nullsink)
# gr.wavfile_source does not have public open() method like gr.wavfile_sink does
# so we have to create a new instance every time :-(
self.audio_player = gr.wavfile_source(filename=self._prev_audio_rec, repeat=True)
self.connect(self.audio_player, self.audio_gain)
print "Audio playback started: ", self._prev_audio_rec
# restart the flowgraph
self.unlock()
return 0
def stop_audio_playback(self):
"""
This function stops the audio playback and reconnects the receiver path.
"""
if self.audio_player == None:
print "Audio playback not active."
return
# stop flowgraph while we re-wire
self.lock()
self.disconnect(self.audio_rr, self.audio_nullsink)
self.disconnect(self.audio_player, self.audio_gain)
# we must destroy the audio player object (new instance created at every play)
del self.audio_player
self.audio_player = None
self.connect(self.audio_rr, self.audio_gain)
print "Audio playback stopped: ", self._prev_audio_rec
# restart the flowgraph
self.unlock()
if __name__ == "__main__":
tb = my_top_block();
tb.start()
tb.qapp.exec_()