Results 1 to 3 of 3

Thread: program controlled GRC

Threaded View

  1. #1
    Join Date
    May 2007
    Beans
    601

    program controlled GRC

    For those not familiar with gnuradio and grc here is a helpful link: http://superkuh.com/gnuradio.html#simple

    Using a simple "hack", some of the grc flow graph can be program controlled by adding your own code. The basic idea is to use a
    function probe in your flow graph. This sets up an independent thread which one can use to interact with the rest of the flow
    graph with one's own code. Of course, one could create their own threads as required. If only one could access the actual arrays in this way, but I digress.

    As an example, I have created a FM radio with seek capabilities. See the attached "Sradio.grc.zip" file.

    To add the seek function, one must add or replace code in the grc generated python file. Here I replace the _probe_probe()
    with my seek code:
    Code:
    		def scan(scanmode):
    		        if scanmode == 1: return
    		        self.set_squelchThreshold(10) # mute while scanning
    			step = 0.2 if scanmode == 2 else -0.2
    			f = self.f + step
    			if f > 108: f = 87.5
    			if f < 87.5: f = 108.1 # must be odd freq
    			self.set_f(f)
    		        
    		# find greatest level in scanmode direction (assumes positive slope)
    		def scantune(scanmode,val):
    		   print "\nscantune %4.1f %f" % (self.f,val)
    		   self.set_variable_qtgui_chooser_0(1)
    		   step = 0.2 if scanmode == 2 else -0.2
    		   while True:	   		
    		   	f=self.f + step
    		   	self.set_f(f)
    		   	time.sleep(0.5)
    		   	newval = self.avgmag2.level() 
    		   	print "%4.2f %f %4.2f %f" % (f-step,val,f,newval)
    		   	if (newval <= val + self.epsilon): break
    		   	val = newval
                       f=self.f - step
    		   self.set_f(f)
    		
    		def _probe_probe():
    			while True:
    				val = self.avgmag2.level()
    				if (val > scanThreshold) and (scanmode <> 1) and (oldval < val):
    				   scantune(scanmode,val)
    				   scanmode = 1
    				else:
    				   self.set_squelchThreshold(squelchThreshold) # unmute
    				   scanmode = self.get_variable_qtgui_chooser_0()
    				   scan(scanmode)
    				   oldval = val
    				try: self.set_probe(val)
    				except AttributeError, e: pass
    				time.sleep(1.0/(5))
    Don't forget indentation is important in python!


    Finally, move the thread start call "_probe_thread.start()" to the end of the initialization code so everything is properly initialized.

    Here is the final code Sradio.py:
    Code:
    #!/usr/bin/env python
    ##################################################
    # Gnuradio Python Flow Graph
    # Title: Sradio
    # Author: Keith Nuesslein
    # Description: Sradio 1.01 FM radio with seek tuning
    # Generated: Fri Sep  7 10:17:42 2012
    ##################################################
    
    from PyQt4 import Qt
    from gnuradio import audio
    from gnuradio import blks2
    from gnuradio import eng_notation
    from gnuradio import gr
    from gnuradio.eng_option import eng_option
    from gnuradio.gr import firdes
    from optparse import OptionParser
    import PyQt4.Qwt5 as Qwt
    import baz
    import sys
    import threading
    import time
    
    class Sradio(gr.top_block, Qt.QWidget):
    
    	def __init__(self, squelchThreshold=-60, deemphasis=75e-6, defaultStation=96.7, lpfCutoff=100000, magAlpha=0.001, sdrFreqCorr=82, srate=1.920e6, rfGain=1, epsilon=.01, scanThreshold=0.3):
    		gr.top_block.__init__(self, "Sradio")
    		Qt.QWidget.__init__(self)
    		self.setWindowTitle("Sradio")
    		self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
    		self.top_scroll_layout = Qt.QVBoxLayout()
    		self.setLayout(self.top_scroll_layout)
    		self.top_scroll = Qt.QScrollArea()
    		self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)
    		self.top_scroll_layout.addWidget(self.top_scroll)
    		self.top_scroll.setWidgetResizable(True)
    		self.top_widget = Qt.QWidget()
    		self.top_scroll.setWidget(self.top_widget)
    		self.top_layout = Qt.QVBoxLayout(self.top_widget)
    		self.top_grid_layout = Qt.QGridLayout()
    		self.top_layout.addLayout(self.top_grid_layout)
    
    
    		##################################################
    		# Parameters
    		##################################################
    		self.squelchThreshold = squelchThreshold
    		self.deemphasis = deemphasis
    		self.defaultStation = defaultStation
    		self.lpfCutoff = lpfCutoff
    		self.magAlpha = magAlpha
    		self.sdrFreqCorr = sdrFreqCorr
    		self.srate = srate
    		self.rfGain = rfGain
    		self.epsilon = epsilon
    		self.scanThreshold = scanThreshold
    
    		##################################################
    		# Variables
    		##################################################
    		self.probe = probe = 0
    		self.f = f = defaultStation
    		self.vol = vol = 1
    		self.variable_qtgui_label_0 = variable_qtgui_label_0 = "%7.3f %23s%6.1f Mhz" % (probe," ",f) 
    		self.variable_qtgui_chooser_0 = variable_qtgui_chooser_0 = 1
    		self.samp_rate = samp_rate = srate
    		self.dec = dec = 8
    
    		##################################################
    		# Blocks
    		##################################################
    		self._vol_layout = Qt.QVBoxLayout()
    		self._vol_label = Qt.QLabel("Volume")
    		self._vol_slider = Qwt.QwtSlider(None, Qt.Qt.Horizontal, Qwt.QwtSlider.BottomScale, Qwt.QwtSlider.BgSlot)
    		self._vol_slider.setRange(0, 10, .5)
    		self._vol_slider.setValue(self.vol)
    		self._vol_slider.setMinimumWidth(200)
    		self._vol_slider.valueChanged.connect(self.set_vol)
    		self._vol_label.setAlignment(Qt.Qt.AlignBottom | Qt.Qt.AlignHCenter)
    		self._vol_layout.addWidget(self._vol_label)
    		self._vol_layout.addWidget(self._vol_slider)
    		self.top_grid_layout.addLayout(self._vol_layout, 2,10)
    		self._f_layout = Qt.QVBoxLayout()
    		self._f_label = Qt.QLabel("FM Tuner")
    		self._f_slider = Qwt.QwtSlider(None, Qt.Qt.Horizontal, Qwt.QwtSlider.BottomScale, Qwt.QwtSlider.BgSlot)
    		self._f_slider.setRange(87.5, 108.1, .2)
    		self._f_slider.setValue(self.f)
    		self._f_slider.setMinimumWidth(200)
    		self._f_slider.valueChanged.connect(self.set_f)
    		self._f_label.setAlignment(Qt.Qt.AlignBottom | Qt.Qt.AlignHCenter)
    		self._f_layout.addWidget(self._f_label)
    		self._f_layout.addWidget(self._f_slider)
    		self.top_grid_layout.addLayout(self._f_layout, 0,1,1,10)
    		self.avgmag2 = gr.probe_avg_mag_sqrd_c(squelchThreshold, magAlpha)
    		self._variable_qtgui_label_0_tool_bar = Qt.QToolBar(self)
    		self._variable_qtgui_label_0_tool_bar.addWidget(Qt.QLabel("Signal"+": "))
    		self._variable_qtgui_label_0_label = Qt.QLabel(str(self.variable_qtgui_label_0))
    		self._variable_qtgui_label_0_tool_bar.addWidget(self._variable_qtgui_label_0_label)
    		self.top_grid_layout.addWidget(self._variable_qtgui_label_0_tool_bar, 3,1,1,10)
    		self._variable_qtgui_chooser_0_options = (0, 1, 2, )
    		self._variable_qtgui_chooser_0_labels = ("DOWN", "STOP", "UP", )
    		self._variable_qtgui_chooser_0_group_box = Qt.QGroupBox("Seekmode")
    		self._variable_qtgui_chooser_0_box = Qt.QHBoxLayout()
    		self._variable_qtgui_chooser_0_button_group = Qt.QButtonGroup()
    		self._variable_qtgui_chooser_0_group_box.setLayout(self._variable_qtgui_chooser_0_box)
    		for i, label in enumerate(self._variable_qtgui_chooser_0_labels):
    			radio_button = Qt.QRadioButton(label)
    			self._variable_qtgui_chooser_0_box.addWidget(radio_button)
    			self._variable_qtgui_chooser_0_button_group.addButton(radio_button, i)
    		self._variable_qtgui_chooser_0_callback = lambda i: self._variable_qtgui_chooser_0_button_group.button(self._variable_qtgui_chooser_0_options.index(i)).setChecked(True)
    		self._variable_qtgui_chooser_0_callback(self.variable_qtgui_chooser_0)
    		self._variable_qtgui_chooser_0_button_group.buttonClicked[int].connect(
    			lambda i: self.set_variable_qtgui_chooser_0(self._variable_qtgui_chooser_0_options[i]))
    		self.top_grid_layout.addWidget(self._variable_qtgui_chooser_0_group_box, 2,1)
    		self.rtl2832_source_0 = baz.rtl_source_c(defer_creation=True, output_size=gr.sizeof_gr_complex)
    		self.rtl2832_source_0.set_verbose(True)
    		self.rtl2832_source_0.set_vid(0x0)
    		self.rtl2832_source_0.set_pid(0x0)
    		self.rtl2832_source_0.set_tuner_name("e4k")
    		self.rtl2832_source_0.set_default_timeout(0)
    		self.rtl2832_source_0.set_use_buffer(True)
    		self.rtl2832_source_0.set_fir_coefficients(([]))
    		
    		self.rtl2832_source_0.set_read_length(0)
    		
    		
    		
    		
    		if self.rtl2832_source_0.create() == False: raise Exception("Failed to create RTL2832 Source: rtl2832_source_0")
    		
    		
    		self.rtl2832_source_0.set_sample_rate(samp_rate)
    		
    		self.rtl2832_source_0.set_frequency(f*1e6)
    		
    		
    		
    		self.rtl2832_source_0.set_auto_gain_mode(False)
    		self.rtl2832_source_0.set_relative_gain(True)
    		self.rtl2832_source_0.set_gain(rfGain)
    		  
    
    		def scan(scanmode):
    		        if scanmode == 1: return
    		        self.set_squelchThreshold(10) # mute while scanning
    			step = 0.2 if scanmode == 2 else -0.2
    			f = self.f + step
    			if f > 108: f = 87.5
    			if f < 87.5: f = 108.1 # must be odd freq
    			self.set_f(f)
    		        
    		# find greatest level in scanmode direction (assumes positive slope)
    		def scantune(scanmode,val):
    		   print "\nscantune %4.1f %f" % (self.f,val)
    		   self.set_variable_qtgui_chooser_0(1)
    		   step = 0.2 if scanmode == 2 else -0.2
    		   while True:	   		
    		   	f=self.f + step
    		   	self.set_f(f)
    		   	time.sleep(0.5)
    		   	newval = self.avgmag2.level() 
    		   	print "%4.2f %f %4.2f %f" % (f-step,val,f,newval)
    		   	if (newval <= val + self.epsilon): break
    		   	val = newval
                       f=self.f - step
    		   self.set_f(f)
    		
    		def _probe_probe():
    			while True:
    				val = self.avgmag2.level()
    				if (val > scanThreshold) and (scanmode <> 1) and (oldval < val):
    				   scantune(scanmode,val)
    				   scanmode = 1
    				else:
    				   self.set_squelchThreshold(squelchThreshold) # unmute
    				   scanmode = self.get_variable_qtgui_chooser_0()
    				   scan(scanmode)
    				   oldval = val
    				try: self.set_probe(val)
    				except AttributeError, e: pass
    				time.sleep(1.0/(5))
    				
    		_probe_thread = threading.Thread(target=_probe_probe)
    		_probe_thread.daemon = True
    		
    		self.low_pass_filter_1 = gr.fir_filter_fff(5, firdes.low_pass(
    			vol*50, samp_rate/dec, 15000, 1000, firdes.WIN_HAMMING, 6.76))
    		self.low_pass_filter_0 = gr.fir_filter_ccf(dec, firdes.low_pass(
    			4, samp_rate, lpfCutoff, 8000, firdes.WIN_HAMMING, 6.76))
    		self.gr_simple_squelch_cc_0 = gr.simple_squelch_cc(squelchThreshold, magAlpha)
    		self.blks2_wfm_rcv_0 = blks2.wfm_rcv(
    			quad_rate=samp_rate/dec,
    			audio_decimation=1,
    		)
    		self.blks2_fm_deemph_0 = blks2.fm_deemph(fs=samp_rate/dec, tau=deemphasis)
    		self.audio_sink_0 = audio.sink(48000, "pulse", True)
    
    		##################################################
    		# Connections
    		##################################################
    		self.connect((self.low_pass_filter_0, 0), (self.avgmag2, 0))
    		self.connect((self.gr_simple_squelch_cc_0, 0), (self.blks2_wfm_rcv_0, 0))
    		self.connect((self.blks2_wfm_rcv_0, 0), (self.blks2_fm_deemph_0, 0))
    		self.connect((self.low_pass_filter_0, 0), (self.gr_simple_squelch_cc_0, 0))
    		self.connect((self.low_pass_filter_1, 0), (self.audio_sink_0, 0))
    		self.connect((self.blks2_fm_deemph_0, 0), (self.low_pass_filter_1, 0))
    		self.connect((self.rtl2832_source_0, 0), (self.low_pass_filter_0, 0))
    		_probe_thread.start()
    
    	def get_squelchThreshold(self):
    		return self.squelchThreshold
    
    	def set_squelchThreshold(self, squelchThreshold):
    		self.squelchThreshold = squelchThreshold
    		self.avgmag2.set_threshold(self.squelchThreshold)
    		self.gr_simple_squelch_cc_0.set_threshold(self.squelchThreshold)
    
    	def get_deemphasis(self):
    		return self.deemphasis
    
    	def set_deemphasis(self, deemphasis):
    		self.deemphasis = deemphasis
    
    	def get_defaultStation(self):
    		return self.defaultStation
    
    	def set_defaultStation(self, defaultStation):
    		self.defaultStation = defaultStation
    		self.set_f(self.defaultStation)
    
    	def get_lpfCutoff(self):
    		return self.lpfCutoff
    
    	def set_lpfCutoff(self, lpfCutoff):
    		self.lpfCutoff = lpfCutoff
    		self.low_pass_filter_0.set_taps(firdes.low_pass(4, self.samp_rate, self.lpfCutoff, 8000, firdes.WIN_HAMMING, 6.76))
    
    	def get_magAlpha(self):
    		return self.magAlpha
    
    	def set_magAlpha(self, magAlpha):
    		self.magAlpha = magAlpha
    		self.avgmag2.set_alpha(self.magAlpha)
    		self.gr_simple_squelch_cc_0.set_alpha(self.magAlpha)
    
    	def get_sdrFreqCorr(self):
    		return self.sdrFreqCorr
    
    	def set_sdrFreqCorr(self, sdrFreqCorr):
    		self.sdrFreqCorr = sdrFreqCorr
    
    	def get_srate(self):
    		return self.srate
    
    	def set_srate(self, srate):
    		self.srate = srate
    		self.set_samp_rate(self.srate)
    
    	def get_rfGain(self):
    		return self.rfGain
    
    	def set_rfGain(self, rfGain):
    		self.rfGain = rfGain
    		self.rtl2832_source_0.set_gain(self.rfGain)
    
    	def get_epsilon(self):
    		return self.epsilon
    
    	def set_epsilon(self, epsilon):
    		self.epsilon = epsilon
    
    	def get_scanThreshold(self):
    		return self.scanThreshold
    
    	def set_scanThreshold(self, scanThreshold):
    		self.scanThreshold = scanThreshold
    
    	def get_probe(self):
    		return self.probe
    
    	def set_probe(self, probe):
    		self.probe = probe
    		self.set_variable_qtgui_label_0("%7.3f %23s%6.1f Mhz" % (self.probe," ",self.f) )
    
    	def get_f(self):
    		return self.f
    
    	def set_f(self, f):
    		self.f = f
    		self.set_variable_qtgui_label_0("%7.3f %23s%6.1f Mhz" % (self.probe," ",self.f) )
    		self._f_slider.setValue(self.f)
    		self.rtl2832_source_0.set_frequency(self.f*1e6)
    
    	def get_vol(self):
    		return self.vol
    
    	def set_vol(self, vol):
    		self.vol = vol
    		self._vol_slider.setValue(self.vol)
    		self.low_pass_filter_1.set_taps(firdes.low_pass(self.vol*50, self.samp_rate/self.dec, 15000, 1000, firdes.WIN_HAMMING, 6.76))
    
    	def get_variable_qtgui_label_0(self):
    		return self.variable_qtgui_label_0
    
    	def set_variable_qtgui_label_0(self, variable_qtgui_label_0):
    		self.variable_qtgui_label_0 = variable_qtgui_label_0
    		self._variable_qtgui_label_0_label.setText(str(self.variable_qtgui_label_0))
    
    	def get_variable_qtgui_chooser_0(self):
    		return self.variable_qtgui_chooser_0
    
    	def set_variable_qtgui_chooser_0(self, variable_qtgui_chooser_0):
    		self.variable_qtgui_chooser_0 = variable_qtgui_chooser_0
    		self._variable_qtgui_chooser_0_callback(self.variable_qtgui_chooser_0)
    
    	def get_samp_rate(self):
    		return self.samp_rate
    
    	def set_samp_rate(self, samp_rate):
    		self.samp_rate = samp_rate
    		self.low_pass_filter_0.set_taps(firdes.low_pass(4, self.samp_rate, self.lpfCutoff, 8000, firdes.WIN_HAMMING, 6.76))
    		self.low_pass_filter_1.set_taps(firdes.low_pass(self.vol*50, self.samp_rate/self.dec, 15000, 1000, firdes.WIN_HAMMING, 6.76))
    		self.rtl2832_source_0.set_sample_rate(self.samp_rate)
    
    	def get_dec(self):
    		return self.dec
    
    	def set_dec(self, dec):
    		self.dec = dec
    		self.low_pass_filter_1.set_taps(firdes.low_pass(self.vol*50, self.samp_rate/self.dec, 15000, 1000, firdes.WIN_HAMMING, 6.76))
    
    if __name__ == '__main__':
    	parser = OptionParser(option_class=eng_option, usage="%prog: [options]")
    	parser.add_option("-t", "--squelchThreshold", dest="squelchThreshold", type="intx", default=-60,
    		help="Set squelchThreshold [default=%default]")
    	parser.add_option("-e", "--deemphasis", dest="deemphasis", type="eng_float", default=eng_notation.num_to_str(75e-6),
    		help="Set deemphasis [default=%default]")
    	parser.add_option("-d", "--defaultStation", dest="defaultStation", type="eng_float", default=eng_notation.num_to_str(96.7),
    		help="Set (Mhz) [default=%default]")
    	parser.add_option("-l", "--lpfCutoff", dest="lpfCutoff", type="long", default=100000,
    		help="Set rf lowpass filter [default=%default]")
    	parser.add_option("-m", "--magAlpha", dest="magAlpha", type="eng_float", default=eng_notation.num_to_str(0.001),
    		help="Set signal mag alpha [default=%default]")
    	parser.add_option("-c", "--sdrFreqCorr", dest="sdrFreqCorr", type="intx", default=82,
    		help="Set sdrFreqCorr [default=%default]")
    	parser.add_option("-r", "--srate", dest="srate", type="eng_float", default=eng_notation.num_to_str(1.920e6),
    		help="Set srate [default=%default]")
    	parser.add_option("-g", "--rfGain", dest="rfGain", type="eng_float", default=eng_notation.num_to_str(1),
    		help="Set rfGain [default=%default]")
    	parser.add_option("-p", "--epsilon", dest="epsilon", type="eng_float", default=eng_notation.num_to_str(.01),
    		help="Set min diff [default=%default]")
    	parser.add_option("-s", "--scanThreshold", dest="scanThreshold", type="eng_float", default=eng_notation.num_to_str(0.3),
    		help="Set scan signal detect threshold [default=%default]")
    	(options, args) = parser.parse_args()
    	qapp = Qt.QApplication(sys.argv)
    	tb = Sradio(squelchThreshold=options.squelchThreshold, deemphasis=options.deemphasis, defaultStation=options.defaultStation, lpfCutoff=options.lpfCutoff, magAlpha=options.magAlpha, sdrFreqCorr=options.sdrFreqCorr, srate=options.srate, rfGain=options.rfGain, epsilon=options.epsilon, scanThreshold=options.scanThreshold)
    	tb.start()
    	tb.show()
    	qapp.exec_()
    	tb.stop()
    The Sradio.py application depends on signal strength comparisons so disabling agc is highly desirable, however, in my case the
    RTL2832 source block will crash if the frequency is changed too quickly. There may also be a need for tweaking some other parameters depending on one's setup.

    EDIT: Choosing the RTL2832 source with the Osmo E4000 device seems to prevents crashing. I will revise the code and post an update at a later time.
    EDIT: I have updated the zip file with the new Osmo E4000 source modifications and added Sradio.py. Use Sradio.py -h for additional options. The GRC file will need to be modified for other than E4000 sources.
    Attached Files Attached Files
    Last edited by xzero1; September 7th, 2012 at 05:06 PM. Reason: code modifications

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •