https://wiki.gnuradio.org/api.php?action=feedcontributions&user=Cmrincon&feedformat=atomGNU Radio - User contributions [en]2024-03-29T05:53:00ZUser contributionsMediaWiki 1.39.5https://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GNU_Radio_in_Python&diff=6423Guided Tutorial GNU Radio in Python2019-12-15T09:14:58Z<p>Cmrincon: update to v3.8.0.0</p>
<hr />
<div>&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
<br />
= Tutorial: Working with GNU Radio in Python =<br />
<br />
== Objectives ==<br />
<br />
* Python Blocks<br />
* OOT modules make the actual apps / functionality (GR is the API!)<br />
* How to add OOTs<br />
* How to add Python blocks with gr_modtool and how to code them<br />
* QPSK mapping<br />
* How to add GRC bindings for block<br />
<br />
== Prerequisites ==<br />
<br />
* Working Installation of GNU Radio 3.8 or later<br />
* [[Guided_Tutorial_GRC|GRC Tutorial]] (Recommended)<br />
* Familiar with Python<br />
<br />
-----<br />
<br />
== 3.1. Intro to Using GNU Radio with Python ==<br />
<br />
This tutorial goes through three parts. The first is how to modify, create, or simply understand the Python generated files GRC produces for us. The second is how to create our own custom out-of-tree (OOT) modules from the ground up. Lastly we go through an actual project to get more practice and build intuition on how we can use GNU Radio in our own project. As with the last tutorial, all the content - pictures, source code, and grc files - is included in the [https://github.com/gnuradio/gr-tutorial gr-tutorial repository] which we should have a local copy if we followed the directions from the [[Guided_Tutorial_GRC|GRC Tutorial]]<br />
<br />
Again we should have a directory with the solutions and a directory with our work as below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
/home/user/gnuradio/tutorials/solutions<br />
/home/user/gnuradio/tutorials/work<br />
</syntaxhighlight><br />
<br />
As a rule, if we hover over the GRC flowgraph images, we will be able to see the corresponding filename. Same applies for other images. Full code files are collapsed with the filename in the collapsed handle.<br />
<br />
=== 3.1.1. GRC Generated Python Files ===<br />
<br />
Let us look at a dial-tone example on the GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png<br />
<br />
When we click the '''Generate''' button, the terminal tells us it produced a .py file so let's open that to examine its code which is reproduced below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: tutorial_three_1<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
from gnuradio.filter import firdes<br />
import sys<br />
import signal<br />
from PyQt5 import Qt<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
<br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("tutorial_three_1")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.samp_rate = samp_rate = 32000<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.audio_sink_0 = audio.sink(samp_rate, '', True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
<br />
<br />
<br />
def main(top_block_cls=tutorial_three_1, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
The first thing for us to realize is that the GRC can generate Python files that we can then modify to do things we wouldn't be able to do in GNU Radio Companion such as perform [[TutorialsSimulations|simulations]]. The libraries available in Python open up a whole new realm of possibilities! For now, we will explore the structure of the GRC Python files so we are comfortable creating more interesting applications.<br />
<br />
=== 3.1.2. Hello World Dissected ===<br />
<br />
While examining the code, we need to get familiar with documentation. GNU Radio uses Doxygen (the software) for the [http://gnuradio.org/doc/doxygen/ GNU Radio Manual]. The easiest way to go through the documentation is to go through the functions that we use so let us simplify our code by only including the bare bones needed to run the dial-tone example.<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env Python3<br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, "")<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass<br />
<br />
</syntaxhighlight><br />
<br />
Let us examine this line by line:<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
</syntaxhighlight><br />
Tells the shell that this file is a Python file and to use the Python interpreter to run this file. Should always be included at the top to run from the terminal.<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
</syntaxhighlight><br />
<br />
Tells Python the modules to include. We must always have '''gr''' to run GNU Radio applications. The audio sink is included in the audio module and the signal_source_f is included in the analog module which is why we include them. [http://legacy.python.org/dev/peps/pep-0008/ PEP8] tells us we should import every module on its own line.<br />
<br />
<syntaxhighlight lang="python"><br />
class my_top_block(gr.top_block):<br />
</syntaxhighlight><br />
Define a class called "my_top_block" which is derived from another class, '''gr.top_block'''. This class is basically a container for the flow graph. By deriving from gr.top_block, we get all the hooks and functions we need to add blocks and interconnect them.<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self):<br />
</syntaxhighlight><br />
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.<br />
<br />
<syntaxhighlight lang="python"><br />
gr.top_block.__init__(self)<br />
</syntaxhighlight><br />
The parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br />
<br />
<syntaxhighlight lang="python"><br />
sample_rate = 32000<br />
ampl = 0.1<br />
</syntaxhighlight><br />
Variable declarations for sampling rate and amplitude that we will later use.<br />
<br />
=== 3.1.3. A Look at Documentation ===<br />
<br />
<syntaxhighlight lang="python"><br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
</syntaxhighlight><br />
Here we are using functions from GNU Radio so let's have a look at the documentation for '''analog.sig_source_f''' which is available in [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html the GNU Radio manual]. We can find it easily by using the search function as below:<br />
<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_search.png<br />
<br />
We can then scroll down to '''Member Function Documentation''' to see how the function is used and the parameters it accepts as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_source.png<br />
<br />
We can see that our function '''analog.sig_source_f''' takes in 5 parameters but in our code we are only using 4. There is no error because the last input '''offset''' is set to "0" by default as shown in the documentation. The first input is the '''sampling_freq''' which we defined as '''sample_rate''' in our code. The second input is asking for a '''gr::analog::gr_waveform_t''' waveform so let's click that [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177 link] to find out more.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_waveform.png<br />
<br />
We can see that there are a couple of options to choose from. In this case we chose '''analog.GR_SIN_WAVE'''. The third input is '''wave_freq''' which we input "350" or "440". The fourth input is '''ampl''' which we defined as '''ampl'''.<br />
<br />
<syntaxhighlight lang="python"><br />
dst = audio.sink(sample_rate, "")<br />
</syntaxhighlight><br />
Because documentation is so important, let's look at another example. Again, we can look at the documentation for '''audio.sink''' which is available on the GNU Radio Manual through the search function:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink.png<br />
<br />
We can then as before scroll down to the '''Member Function Documentation''' to see the parameters it accepts:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink_detail.png<br />
<br />
This time we have 3 inputs with the last being optional. In our code, for the first input '''sampling_rate''' we used are '''sample_rate''' variable. In the second input, we have a choice for what device to use for audio output. If we leave it alone as "" then it'll choose the default on our machine.<br />
<br />
=== 3.1.4. Connecting the Block Together ===<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
</syntaxhighlight><br />
The general syntax for connecting blocks is self.connect(block1, block2, block3, ...) which would connect the output of block1 with the input of block2, the output of block2 with the input of block3 and so on. We can connect as many blocks as we wish with one connect() call. However this only work when there is a one-to-one correspondence. If we go back to our initial flowgraph, there are 2 inputs to the '''Audio Sink''' block. The way to connect them is by using the syntax above. The first line connects the only output of src0 (350 Hz waveform) to the first input of dst (Audio Sink). The second line connects the only output of src1 (440 Hz waveform) to the second input of dst (Audio Sink). The code so far is equivalent to the flowgraph we have created in the beginning; the rest of the lines simply start the flowgraph and provide a keyboard interrupt.<br />
<br />
<syntaxhighlight lang="python"><br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except KeyboardInterrupt:<br />
pass<br />
</syntaxhighlight><br />
<br />
Luckily we are past the early years of GNU Radio when there was no GRC to make the Python files for us. Nowadays we can simply click things together in GRC instead of having to write code in Python to build flowgraphs. Still, a good understanding of what is going on every time we run GRC is good to know as it gives us more control of what we want the program to do.<br />
<br />
=== 3.1.5. Modifying the GRC Generated Python File ===<br />
<br />
For instance, what if we wanted to change variables such as frequency and amplitude when a certain event occurs. How do we implement if statements, state machines, etc in GNU Radio? One way is to create our own blocks which we will delve into at length later. Another is to modify our GRC generated python file.<br />
<br />
Our friend heard we were into RF so being cheap he has asked us to power his house using RF. He wants us to give him high power during the day so he can watch TV and play video games while at night give him low power so he power his alarm clock to wake up for work in the morning. We first need to setup the clock which keeps track of the day and gives us 1 for day or 0 for night. Once we get the time we can send him power through a sine wave using our massive terawatt amplifier and massive dish in our backyard. We did the calculations and we want to pulse at 1kHz, 1 amplitude during the day and 100Hz, 0.3 amplitude at night. Here's what we came up with in GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else.png<br />
<br />
<span style="color:blue">- '''Frequency''' to "freq", '''Amplitude''' to "ampl"</span><br /><br />
<span style="color:red">- '''ID''' to "probe"</span><br /><br />
- Everything else is visible<br />
<br />
The top section keeps track of time and will switch from 0 to 1 while the bottom section sends the pulse. The problem we encounter however is that there is no if-statement block. Sure we can tie the probe to the frequency as we did in tutorial2 for the singing sine wave but that only allows changing by a factor. What if we wanted to change multiple things and not by a linear factor? Let's start by running the flowgraph to make sure we get the output as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else_output.png<br />
<br />
Now we can open up the GRC generated python file if_else.py which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.get_number()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We are only concerned about a couple of parts namely the part where the probe is being read:<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
We can see that the variable '''val''' is obtaining the value of the probe block. We can write our if-else statement here based on the value of '''val''' to change the amplitude and frequency of our sine wave. But how do we change the frequency and amplitude? We can use the part where the '''QT GUI Entry''' updates the flowgraph. For the variable freq:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
</syntaxhighlight><br />
<br />
and for the variable ampl:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
</syntaxhighlight><br />
We can see that the functions set_ampl and set_freq can be used for just that - setting the amplitude and the frequency. Thus we can go back and modify our probe function with the if-else statement to give power to our friend.<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_funct ion_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
<br />
Now there is one more thing we need to take care of. GRC has compiled the python file in the order of creation of the elements, which was okay as long as there were no crossreferences. With the introduced adaptation (calling set_ampl and set_freq inside the _variable_function_probe_0_probe()) we need to fix the order of declarations. As set_ampl and set_freq both modify parameters of analog_sig_source_x_0_0 but analog_sig_source_x_0_0 is not instantiated before line 150, we have to move the declaration of the _variable_function_probe_0_probe() and everything related below that.<br />
<br />
<syntaxhighlight lang="python"><br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
</syntaxhighlight><br />
<br />
Full code copied below:<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print (val)<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We can then simply run our flowgraph from outside of GRC using<br />
<br />
<pre><br />
$ python3 if_else_mod.py<br />
</pre><br />
We should be able to see the numbers 0 and 1 on the terminal and the sine wave changing amplitude and frequency as the numbers change.<br />
<br />
This tutorial is merely an introduction on using python in GNU Radio, for a more advanced tutorial see [[TutorialsWritePythonApplications]] .<br />
<br />
== 3.2. Where Do Blocks Come From? ==<br />
<br />
Now that we have covered some of the ways we can modify our GRC generated Python files, we can see how to make our own blocks to add functionality to suit our specific project needs. Let us start off simple to get a feel for the waters. We will be making a block that is able to multiply a signal by the number we specify. The first thing we need to realize is that GNU Radio comes with gr_modtool, a utility that makes creating OOT Modules easy. Let us open up a terminal and begin in a project folder for the tutorial three.<br />
<br />
=== 3.2.1. Using gr_modtool ===<br />
<br />
Before we begin, we need to figure out what the commands for gr_modtool are so let's ask for help.<br />
<br />
<pre>$ gr_modtool --help<br />
Usage: gr_modtool [OPTIONS] COMMAND [ARGS]...<br />
<br />
A tool for editing GNU Radio out-of-tree modules.<br />
<br />
Options:<br />
--help Show this message and exit.<br />
<br />
Commands:<br />
add Adds a block to the out-of-tree module.<br />
disable Disable selected block in module.<br />
info Return information about a given module<br />
makeyaml Generate YAML files for GRC block bindings.<br />
newmod Create new empty module, use add to add blocks.<br />
rename Rename a block inside a module.<br />
rm Remove a block from a module.<br />
update Update the grc bindings for a block<br />
<br />
Manipulate with GNU Radio modules source code tree. Call it without<br />
options to run specified command interactively<br />
<br />
</pre><br />
We immediately see there are many commands available. In this tutorial we will only cover '''newmod''' and '''add'''; however, the thorough explanation should enable smooth usage of the other gr_modtool commands without guidance.<br />
<br />
First, we notice that in addition to getting help seeing the commands we can use, we can also request more information on individual commands. Let us start with newmod as that is the command to create a new out-of-tree module.<br />
<br />
<pre><br />
$ gr_modtool newmod --help<br />
Usage: gr_modtool newmod [OPTIONS] MODULE-NAME<br />
<br />
Create a new out-of-tree module<br />
<br />
The argument MODULE-NAME is the name of the module to be added.<br />
<br />
Options:<br />
--srcdir TEXT Source directory for the module template.<br />
-d, --directory TEXT Base directory of the module. Defaults to the cwd.<br />
--skip-lib Don't do anything in the lib/ subdirectory.<br />
--skip-swig Don't do anything in the swig/ subdirectory.<br />
--skip-python Don't do anything in the python/ subdirectory.<br />
--skip-grc Don't do anything in the grc/ subdirectory.<br />
--scm-mode [yes|no|auto] Use source control management [ yes | no | auto ]).<br />
-y, --yes Answer all questions with 'yes'. This can<br />
overwrite and delete your files, so be careful.<br />
--help Show this message and exit.<br />
<br />
</pre><br />
Now that we have read over the list of commands for newmod, we can deduce that the one we want to pick is -n which is the default so we can simply type the MODULE_NAME after newmod. It is actually advised to avoid using "-n" as for other commands it can override the auto-detected name. For now, let's ignore the other options.<br />
<br />
=== 3.2.2. Setting up a new block ===<br />
<br />
<pre><br />
$ gr_modtool newmod tutorial<br />
Creating out-of-tree module in ./gr-tutorial... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.<br />
</pre><br />
We should now see a new folder, gr-tutorial, in our current directory. Let's examine the folder to figure out what gr_modtool has done for us.<br />
<br />
<pre><br />
gr-tutorial$ ls<br />
apps cmake CMakeLists.txt docs examples grc include lib Python swig<br />
</pre><br />
Since we are dealing with Python in this tutorial we only need to concern ourselves with the Python folder and the grc folder. Before we can dive into code, we need to create a block from a template. There are actually four different types of Python blocks, however it's a little too soon to discuss that. We will dive into the synchronous 1:1 input to output block to make explaining things easier (this is a block that outputs as many items as it gets as input, but don't worry about this right now).<br />
<br />
Now we know the language we want to write our block in (Python) and the type of block (synchronous block) we can now add a block to our module. Again, we should run the gr_modtool help command until we are familiar with the different commands. We see that the '''add''' command is what we are looking for. Now we need to run help on the add command in order to see what we need to enter given the information we have so far.<br />
<br />
<pre><br />
gr-tutorial$ gr_modtool help add<br />
... (General Options from Last Help)<br />
Add module options:<br />
-t BLOCK_TYPE, --block-type=BLOCK_TYPE<br />
One of sink, source, sync, decimator, interpolator,<br />
general, tagged_stream, hier, noblock.<br />
--license-file=LICENSE_FILE<br />
File containing the license header for every source<br />
code file.<br />
--argument-list=ARGUMENT_LIST<br />
The argument list for the constructor and make<br />
functions.<br />
--add-Python-qa If given, Python QA code is automatically added if<br />
possible.<br />
--add-cpp-qa If given, C++ QA code is automatically added if<br />
possible.<br />
--skip-cmakefiles If given, only source files are written, but<br />
CMakeLists.txt files are left unchanged.<br />
-l LANG, --lang=LANG<br />
Language (cpp or Python)<br />
</pre><br />
We can see the '''-l LANG''' and the '''-t BLOCK_TYPE''' are relevant for our example. Thus when creating our new block, we know the command. When prompted for a name simply enter "multiply_py_ff", when prompted for an argument list enter "multiple", and when prompted for Python QA (Quality Assurance) code type "y", or just hit enter (the capital letter is the default value).<br />
<br />
<pre>gr-tutorial$ gr_modtool add -t sync -l python<br />
GNU Radio module name identified: tutorial<br />
Language: Python<br />
Enter name of block/code (without module name prefix): multiply_py_ff<br />
Block/code identifier: multiply_py_ff<br />
Enter valid argument list, including default arguments: multiple<br />
Add Python QA code? [Y/n] y<br />
Adding file 'Python/multiply_py_ff.py'...<br />
Adding file 'Python/qa_multiply_py_ff.py'...<br />
Editing Python/CMakeLists.txt...<br />
Adding file 'grc/tutorial_multiply_py_ff.yml'...<br />
Editing grc/CMakeLists.txt...<br />
</pre><br />
We notice 5 changes: Two changes to CMakeLists.txt files, one new file <code>qa_multiply_py_ff.py</code> which is used to test our code, one new file <code>multiply_py_ff.py</code> which is the functional part, and one new file <code>tutorial_multiply_py_ff.xml</code>, which is used to link the block to the GRC. Again all this happens in the Python and grc subfolders.<br />
<br />
==== 3.2.2.1. What's with the <code>_ff</code>? ====<br />
<br />
For blocks with strict types, we use suffixes to declare the input and output types. This block operates on floats, so we give it the suffix <code>_ff</code>: Float in, float out. Other suffixes are <code>_cc</code> (complex in, complex out), or simply <code>_f</code> (a sink or source with no in- or outputs that uses floats). For a more detailed description, see the [[FAQ]] or the [[BlocksCodingGuide]].<br />
<br />
=== 3.2.3. Modifying the Python Block File ===<br />
<br />
Let's begin with the multiply_py_ff.py file found in the Python folder. Opening it without any changes gives the following:<br />
<br />
<syntaxhighlight lang="python"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class multiply_py_ff(gr.sync_block):<br />
"""<br />
docstring for block multiply_py_ff<br />
"""<br />
def __init__(self, multiple):<br />
gr.sync_block.__init__(self,<br />
name="multiply_py_ff",<br />
in_sig=[<+numpy.float+>],<br />
out_sig=[<+numpy.float+>])<br />
self.multiple = multiple<br />
<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
Let's take this one line by line as our first Python examples. We are already familiar with the imports so we will skip those lines. We are familiar with the constructor (init) of Python so can immediately see that if we want to use our variable "multiple", we need to add another line. Let us not forget to preserve those spaces as some code editors like to add tabs to new lines. How do we use the variable multiple?<br />
<br />
How to use variable multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, multiple):<br />
self.multiple = multiple<br />
gr.sync_block.__init__(self,<br />
</syntaxhighlight><br />
<br />
<br />
We notice that there are "&lt;''...''&gt;" scattered in many places. These placeholders are from gr_modtool and tell us where we need to alter things<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[<+numpy.float+>]<br />
out_sig=[<+numpy.float+>]<br />
</syntaxhighlight><br />
The '''gr.sync_block.''init''''' takes in 4 inputs: self, name, and the size/type of the input and output vectors. First, we want to make the item size a single precision float or numpy.float32 by removing the "&lt;''" and the "''&gt;". If we wanted vectors, we could define those as in_sig=[(numpy.float32,4),numpy.float32]. This means there are two input ports, one for vectors of 4 floats and the other for scalars. It is worth noting that if in_sig contains nothing then it becomes a source block, and if out_sig contains nothing it becomes a sink block (provided we change return <code>len(output_items[0])</code> to return <code>len(input_items[0])</code> since output_items is empty). Our changes to the first placeholders should appear as follows:<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[numpy.float32]<br />
out_sig=[numpy.float32]<br />
</syntaxhighlight><br />
The other piece of code that has the placeholders is in the work function but let us first get a better understanding of the work function:<br />
<br />
<syntaxhighlight lang="python"><br />
def work(self, input_items, output_items)<br />
</syntaxhighlight><br />
The work function is where the actual processing happens, where we want our code to be. Because this is a sync block, the number of input items always equals the number of output items because synchronous block ensures a fixed output to input rate. There are also decimation and interpolation blocks where the number of output items are a user specified multiple of the number of input items. We will further discuss when to use what block type in the third section of this tutorial. For now we look at the placeholder:<br />
<br />
<syntaxhighlight lang="python"><br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
The "in0" and "out" simply store the input and output in a variable to make the block easier to write. The signal processing can be anything including if statements, loops, function calls but for this example we only need to modify the out[:] = in0 line so that our input signal is multiplied by our variable multiple. What do we need to add to make the in0 multiply by our multiple?<br />
<br />
How to Multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
out[:] = in0*self.multiple<br />
</syntaxhighlight><br />
<br />
That's it! Our block should now be able to multiply but to be sure we have these things called Quality Assurance tests!<br />
<br />
=== 3.2.4. QA Tests ===<br />
<br />
Now we need to test it to make sure it will run correctly when we install it unto GNU Radio. This is a very important step and we should never forget to include tests with our code! Let us open up qa_multiply_py_ff.py which is copied below:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
from multiply_py_ff import multiply_py_ff<br />
<br />
class qa_multiply_py_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
# set up fg<br />
self.tb.run ()<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_multiply_py_ff)<br />
</syntaxhighlight><br />
gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. The only part we need to worry about is the def test_001_t function. We know we need input data so let us create data. We want it to be in the form of a vector so that we can test multiple values at once. Let us create a vector of floats<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
</syntaxhighlight><br />
We also need output data so we can compare the input of the block to ensure that it is doing what we expect it to do. Let us multiply by 2 for simplicity.<br />
<br />
<syntaxhighlight lang="python">expected_result = (0, 2, -4, 11, -1)<br />
</syntaxhighlight><br />
Now we can create a flowgraph as we have when we first introduced using Python in GNU Radio. We can use the blocks library specifically the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__source__f.html vector_source_f] function and the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__sink__f.html vector_sink_f] function which are linked to the doxygen manual which we should be able to read and understand. Let us assign three variables "src", "mult", and "snk" to the blocks. The first is shown below:<br />
<br />
<syntaxhighlight lang="python">src = blocks.vector_source_f(src_data)</syntaxhighlight><br />
The rest are hidden below as an exercise:<br />
<br />
What do we assign snk and mult?<br />
<br />
<syntaxhighlight lang="python">mult = multiply_py_ff(2)<br />
snk = blocks.vector_sink_f()<br />
</syntaxhighlight><br />
<br />
<br />
Now we need to connect everything as src <s>&gt; mult</s>&gt; snk. Instead of using self.connect as we did in our other blocks we need to use self.tb.connect because of the setUp function. Below is how we would connect the src block to the mult block.<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (src, mult)<br />
</syntaxhighlight><br />
<br />
How would we connect the other blocks together?<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (mult, snk)<br />
</syntaxhighlight><br />
<br />
<br />
Then we can run the graph and store the data from the sink as below:<br />
<br />
<syntaxhighlight lang="python">self.tb.run ()<br />
result_data = snk.data ()<br />
</syntaxhighlight><br />
Lastly we can run our comparison function that will tell us whether the numbers match up to 6 decimal places. We are using the assertFloatTuplesAlmostEqual instead of the "regular assert functions"https://docs.python.org/2/library/unittest.html#assert-methods included in python's unittest because there may be situations where we cannot get a=b due to rounding in floating point numbers.<br />
<br />
<syntaxhighlight lang="python"><br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
All together the new test_001_t function should appear as below:<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
expected_result = (0, 2, -4, 11, -1)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
snk = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, snk)<br />
self.tb.run ()<br />
result_data = snk.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
<br />
We can then go to the python directory and run:<br />
<br />
<pre><br />
gr-tutorial/python$ python qa_multiply_py_ff.py<br />
.<br />
----------------------------------------------------------------------<br />
Ran 1 test in 0.004s<br />
<br />
OK<br />
</pre><br />
<br />
While we are here, we should take a break to change one of the numbers in the src_data to ensure that the block is actually checking the values and to simply see what an error looks like. Python allows for really quick testing of blocks without having to compile anything; simply change something and re-run the QA test.<br />
<br />
=== 3.2.5. YML Files ===<br />
<br />
At this point we should have written a Python block and a QA test for that block. The next thing to do is edit the XML file in the grc folder so that we can get another step closer to using it in GRC. GRC uses the XML files for all the options we see. We actually don't need to write any Python or C++ code to have a block display in GRC but of course if we would connect it, it wouldn't do anything but give errors. We should get out of the python folder and go to the grc folder where all the XML files reside. There is a tool in gr_modtool called makexml but it is only available for C++ blocks. Let us open the tutorial_multiply_py_ff.xml file copied below:<br />
<br />
<syntaxhighlight lang="YAML" line="line"><br />
id: tutorial_multiply_py_ff<br />
label: multiply_py_ff<br />
category: '[tutorial]'<br />
<br />
templates:<br />
imports: import tutorial<br />
make: tutorial.multiply_py_ff(${multiple})<br />
<br />
# Make one 'parameters' list entry for every Parameter you want settable from the GUI.<br />
# Sub-entries of dictionary:<br />
# * id (makes the value accessible as \$keyname, e.g. in the make entry)<br />
# * label<br />
# * dtype <br />
parameters:<br />
- id: ...<br />
label: ...<br />
dtype: ...<br />
- id: ...<br />
label: ...<br />
dtype: ...<br />
<br />
# Make one 'inputs' list entry per input. Sub-entries of dictionary:<br />
# * label (an identifier for the GUI)<br />
# * domain<br />
# * dtype<br />
# * vlen<br />
# * optional (set to 1 for optional inputs) <br />
inputs:<br />
- label: ...<br />
domain: ...<br />
dtype: ...<br />
vlen: ...<br />
<br />
# Make one 'outputs' list entry per output. Sub-entries of dictionary:<br />
# * label (an identifier for the GUI)<br />
# * dtype<br />
# * vlen<br />
# * optional (set to 1 for optional inputs) <br />
- label: ...<br />
domain: ...<br />
dtype: ... #!-- e.g. int, float, complex, byte, short, xxx_vector, ...--<br />
<br />
file_format: 1<br />
</syntaxhighlight><br />
<br />
We can change the name that appears and the category it will appear in GRC. The category is where the block will be found in GRC. Examples of categories tag are '''Audio''' and '''Waveform Generators''' used in previous examples. Examples of names tag are the '''QT GUI Time Sink''' or the '''Audio Sink'''. Again, we can go through the file and find the modtool place holders. The first is copied below:<br />
<br />
<syntaxhighlight lang="YAML" line="line"><br />
# Make one 'parameters' list entry for every Parameter you want settable from the GUI.<br />
# Sub-entries of dictionary:<br />
# * id (makes the value accessible as \$keyname, e.g. in the make entry)<br />
# * label<br />
# * dtype <br />
</syntaxhighlight><br />
This is referring to the parameter that we used in the very beginning when creating our block: the variable called "multiple". We can fill it in as below:<br />
<br />
<syntaxhighlight lang="YAML" line="line"> <br />
parameters:<br />
- id: multiple<br />
label: Multiple<br />
dtype: float<br />
</syntaxhighlight><br />
The next placeholder can be found in the inputs and outputs tags:<br />
<br />
<syntaxhighlight lang="YAML" line="line"> <br />
inputs:<br />
- label: in<br />
dtype: float<br />
<br />
outputs: <br />
- label: out<br />
dtype: float #!-- e.g. int, float, complex, byte, short, xxx_vector, ...--<br />
</syntaxhighlight><br />
We can see that it is asking for a type so we can simply erase everything in the tag and replace it with "float" for both the source and the sink blocks. That should do it for this block. The best way to get more experience writing xml files is to look at the source code of previously made blocks such as the existing multiple block. Let's go back to this and use the documentation tag!<br />
<br />
=== 3.2.6. Installing Python Blocks ===<br />
<br />
Now that we have edited the XML file; we are ready to install the block into the GRC. First, we need to get out of the /grc directory and create a build directory called "build". Inside the build directory, we can then run a series of commands:<br />
<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
<br />
We should then open up the GRC and take a look at the new block.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_blockname.png<br />
<br />
We can see the category and the block name. When we drag it into the GRC workspace, we can see the multiple variable we set in the param tag of the XML file.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_param.png<br />
<br />
Now that we can see our block, let us test it to make sure it works. Below is an example of one of the many ways to check whether it is actually multiplying.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_2.png<br />
<br />
== 3.3. My QPSK Demodulator for Satellites ==<br />
<br />
Now that we have a better understanding on how to add new blocks to GNU Radio for use in the GRC, we can do another example. This time we will be creating a QPSK demodulator block using the same process as before in the same module. Let's first setup the scenario. There is a "noise source" that outputs data in complex float format but let's pretend it comes from a satellite being aimed at our computer. Our secret agent insider tells us this particular satellite encodes digital data using QPSK modulation. We can decode this using a QPSK demodulator that outputs data into bytes. Our insider tells us the space manual doesn't specify whether it's gray code or not. We want to read the bytes using a time sink. What would our flowgraph look like?<br />
<br />
Incomplete Flowgraph...<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_3.png<br />
<br />
<br />
Now that we know the input type, output type, and parameters, we can ask the question we skipped with our multiply_py_ff block. What type of block do we want?<br />
<br />
=== 3.3.1. Choosing a Block Type ===<br />
<br />
The GNU Radio scheduler optimizes for throughput (as opposed to latency). To do this it needs to know the number of samples each block is inputting and outputting thus came the creation of decimation and interpolation blocks where we specify the multiple factor. This is directly related to the sampling rate discussion we had in tutorial 2 where these special types of blocks are able to change the sampling rate to something else in order to comply with a specific piece of hardware such as our soundcard or a specific modulation/demodulation.<br />
<br />
* Synchronous (1:1) - Number of items at input port equals the number of items at output port. An example is the multiply_py_ff block. <br />
* Decimation (N:1) - Number of input items is a fixed multiple of the number of output items. Examples include filters such as decimating high pass and low pass filters (e.g., for sampling rate conversion).<br />
* Interpolation (1:M) - Number of output items is a fixed multiple of the number of input items. An example is the Interpolating FIR Filter.<br />
* General/Basic (N:M) - Provides no relation between the number of input items and the number of output items. An example is the rational resampler block which can be either a sync, decimator, or interpolator.<br />
<br />
When we insert data into our block, do we need more samples or less samples? Or put another way, should our sample rate change?<br />
<br />
What Type of Block Should we Use?<br />
* Sync Block or Basic Block<br />
<br />
<br />
=== 3.3.2. Adding Another Block to our OOT Module ===<br />
<br />
Now we know everything we need to know to create the block in gr_modtool. As a refresher, what would our gr_modtool command be?<br />
<br />
gr_modtool command...<br />
<pre><br />
gr-tutorial$ gr_modtool add -t sync -l python<br />
<br />
name: qpsk_demod_py_cb (c for complex input and b for byte/char/int8 output)<br /><br />
args: gray_code<br /><br />
QA Code: y<br />
</pre><br />
<br />
<br />
Now we have our files setup so we can begin with editing the qpsk_demod_py.py file in the /python folder<br />
<br />
=== 3.3.3. The QPSK Demodulator ===<br />
<br />
The code here is a little more than we have done before so let's take it in steps. First what do we expect our block to do? We can start with a constellation plot which shows the four different quadrants and their mappings to bit 0 and bit 1<br />
<br />
With [http://en.wikipedia.org/wiki/Phase-shift_keying#Quadrature_phase-shift_keying_.28QPSK.29 Gray coding] (adjacent only flips by 1). Note that the mapping we use here is different from the mapping on wikipedia:<br />
<br />
<br />
(-1+1j) 10 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 11 | 01 (1-1j)<br />
<br />
Without Gray coding:<br />
<br />
(-1+1j) 11 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 10 | 01 (1-1j)<br />
<br />
We can see that we will need to write an if-else statement to take into account the gray_code variable. We will also need four other if-else statements inside the main if-else so that we can pick the mapping. Our pseudocode will look as follows:<br />
<br />
if gray_code<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "11" = 3<br />
else<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "10" = 2<br />
<br />
So we have everything we need to implement. Let's go ahead and fill in our gr_modtoool placeholders. We can begin with def init. There are three changes. How do we use the variable gray_code outside the function (similar to what we did with multiple in the last example)? What are the input and output types in [http://docs.scipy.org/doc/numpy/user/basics.types.html? numpy]<br />
<br />
Changes to def ''init''...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
</syntaxhighlight><br />
<br />
Once we have our constructor setup, we can go onto the work function. For simplicity and beauty, let us call the pseudocode we made above a function "get_minimum_distance" that takes samples as input arguments. In our multiply_py_ff example, we took all the samples and multiplied them with with out[:] = in0*self.multiple. The in0 is actually a vector so contains many samples within it. The multiply example required the same operation for each sample so it was okay to simply operate on the entire vector but now we need to have different operations per sample so what do we do?<br />
<br />
How can we operate on samples in a vector?<br />
* loops!<br />
<br />
<syntaxhighlight lang="python"><br />
for i in range(0, len(output_items[0])):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
</syntaxhighlight><br />
<br />
<br />
Now we can move onto the get_minimum_distances(self, sample) function. We already have pseudo code so the next step is translating to Python. Below is a snip of what the code can look like. Again there are multiple ways to do this<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
</syntaxhighlight><br />
Let us try to fill in the other cases for gray code and non-gray code. Below is what the entire file Python file can look like once complete:<br />
<br />
qpsk_demod_py_cb.py<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class qpsk_demod_py(gr.sync_block):<br />
"""<br />
docstring for block qpsk_demod_py<br />
"""<br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
<br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 3 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
else:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 3 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 2 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
<br />
for i in range(0, len(in0)):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
<br />
<br />
Now that we have code, we know what's next!<br />
<br />
=== 3.3.4. Multiple QA Tests ===<br />
<br />
We can test our qpsk_demod_py for when it is in gray_code mode and when it's not in gray_code mode. To do that we need to setup multiple tests in our single QA file. QA tests generally follow the same setup from before. We select some inputs as tests and we check them against what we expect the outputs to be. The only difference from the multiply qa test is that this qa test requires more cases. There are four quadrants we need to map and two modes so in total there are eight test cases. We can open up our qa_qpsk_demod_py_ff.py file to change things.<br />
<br />
We can copy the def test_001_t from the qa_multiply_py_ff code which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def test_001_t (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (-6, 8, -11, 4, 6)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
This time we are working with a complex input so our src = blocks.vector_source_f must change. If we use the search bar in the manual we can find the other options:<br />
<br />
PIC of SEARCH<br />
<br />
b - bytes/unsigned char/int8<br /><br />
c - complex<br /><br />
f - float<br /><br />
i - int<br /><br />
s - short<br />
<br />
What do we change our source and sink vectors to?<br /><br />
src = blocks.vector_source_c (src_data)<br /><br />
dst = blocks.vector_sink_b ()<br /><br />
<br />
<br />
Before we move onto actual test cases, let us decide which mode we are testing for the test_001_t. We can create a new variable and assign it False (translates to 0) to test non-Gray code<br />
<br />
<syntaxhighlight lang="python"><br />
gray_code = False<br />
</syntaxhighlight><br />
Once we know we want to test non gray_code mappings, we can refer to our chart above and start placing in the proper inputs and outputs into the src_data and the expected_results. For instance if we were testing only two cases for non gray_code, we would do:<br />
<br />
<syntaxhighlight lang="python" line="line">src_data = ((-1-1j), (-1+1j))<br />
expected_result = (2, 3)<br />
</syntaxhighlight><br />
Last thing to do is call upon our new block in the "qpsk =" line and pass it the gray_code parameter<br />
<br />
qpsk = ?<br />
* qpsk = qpsk_demod_py_cb (gray_code)<br />
<br />
Now that we are done with the non gray_code test, we can simply create another test "def test_002_t (self):" and copy the contents underneath making sure that for this test we set gray_code = True and change the expected_result so it matches gray_code mapping. The full test is copied below:<br />
<br />
Full QA QPSK Demod Code...<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import numpy<br />
from qpsk_demod_py_cb import qpsk_demod_py<br />
<br />
class qa_qpsk_demod (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
gray_code = False<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (2, 3, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
def test_002_t (self):<br />
gray_code = True<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (3, 2, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qpsk_demod_py_cb.xml")<br />
</syntaxhighlight><br />
<br />
<br />
We can then run the test in Python and all should say something similar to:<br />
<br />
Ran 2 tests in 0.005s<br />
<br />
OK<br />
<br />
So once we verify it works as expected, we can then edit our XML file so that it is usable inside GRC.<br />
<br />
=== 3.3.5. XML Mods, Installation, and Running ===<br />
<br />
This XML is very similar to the XML file for the multiply_py_ff block so all we need to do is set the gray_code parameter and pick the correct input (complex) and output (byte) types. A copy of the full XML file is below:<br />
<br />
XML File for QPSK Demod<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>qpsk_demod_py</name><br />
<key>tutorial_qpsk_demod_py</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.qpsk_demod_py($gray_code)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<type>int</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<!-- e.g. int, float, complex, byte, short, xxx_vector, ...--><br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</block><br />
<br />
</syntaxhighlight><br />
<br />
<br />
We can then install as we did for the multiply block however we need to rerun cmake in order to take into account the new block:<br />
<pre><br />
cd build<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
</pre><br />
Then we can open up our GRC file from the beginning and place our missing block we just made.<br />
<br />
What is the Expected Output?<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_4.png<br />
<br />
== 3.4. Conclusion ==<br />
<br />
And that is it for now with Python. Let us know your thoughts before going on to the [[Guided_Tutorial_GNU_Radio_in_C++|C++ tutorial]].<br />
<br />
=== 3.4.1. Questions We Should Now be Able to Answer ===<br />
<br />
1. How do we set input- and output signatures in Python blocks?<br /><br />
2. Consider this I/O signature: (FIXME). Which input types and output types does it take?<br />
<br />
=== 3.4.2. Links to Further Information ===<br />
<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/BlocksCodingGuide Blocks Coding Guide]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModules Out-of-Tree Modules]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/TutorialsWritePythonApplications Writing Python Applications]<br />
<br />
== 3.5. Candidates for Future Sections ==<br />
<br />
Possible topics we may want to add depending on feedback and questions on the mailing list<br /><br />
- How to add documentation to blocks<br /><br />
- Constructor arguments, History, Output Multiple<br />
<br />
<br />
-----<br />
<br />
&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GNU_Radio_in_Python&diff=6422Guided Tutorial GNU Radio in Python2019-12-15T08:57:49Z<p>Cmrincon: update to v3.8.0.0</p>
<hr />
<div>&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
<br />
= Tutorial: Working with GNU Radio in Python =<br />
<br />
== Objectives ==<br />
<br />
* Python Blocks<br />
* OOT modules make the actual apps / functionality (GR is the API!)<br />
* How to add OOTs<br />
* How to add Python blocks with gr_modtool and how to code them<br />
* QPSK mapping<br />
* How to add GRC bindings for block<br />
<br />
== Prerequisites ==<br />
<br />
* Working Installation of GNU Radio 3.8 or later<br />
* [[Guided_Tutorial_GRC|GRC Tutorial]] (Recommended)<br />
* Familiar with Python<br />
<br />
-----<br />
<br />
== 3.1. Intro to Using GNU Radio with Python ==<br />
<br />
This tutorial goes through three parts. The first is how to modify, create, or simply understand the Python generated files GRC produces for us. The second is how to create our own custom out-of-tree (OOT) modules from the ground up. Lastly we go through an actual project to get more practice and build intuition on how we can use GNU Radio in our own project. As with the last tutorial, all the content - pictures, source code, and grc files - is included in the [https://github.com/gnuradio/gr-tutorial gr-tutorial repository] which we should have a local copy if we followed the directions from the [[Guided_Tutorial_GRC|GRC Tutorial]]<br />
<br />
Again we should have a directory with the solutions and a directory with our work as below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
/home/user/gnuradio/tutorials/solutions<br />
/home/user/gnuradio/tutorials/work<br />
</syntaxhighlight><br />
<br />
As a rule, if we hover over the GRC flowgraph images, we will be able to see the corresponding filename. Same applies for other images. Full code files are collapsed with the filename in the collapsed handle.<br />
<br />
=== 3.1.1. GRC Generated Python Files ===<br />
<br />
Let us look at a dial-tone example on the GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png<br />
<br />
When we click the '''Generate''' button, the terminal tells us it produced a .py file so let's open that to examine its code which is reproduced below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: tutorial_three_1<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
from gnuradio.filter import firdes<br />
import sys<br />
import signal<br />
from PyQt5 import Qt<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
<br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("tutorial_three_1")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.samp_rate = samp_rate = 32000<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.audio_sink_0 = audio.sink(samp_rate, '', True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
<br />
<br />
<br />
def main(top_block_cls=tutorial_three_1, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
The first thing for us to realize is that the GRC can generate Python files that we can then modify to do things we wouldn't be able to do in GNU Radio Companion such as perform [[TutorialsSimulations|simulations]]. The libraries available in Python open up a whole new realm of possibilities! For now, we will explore the structure of the GRC Python files so we are comfortable creating more interesting applications.<br />
<br />
=== 3.1.2. Hello World Dissected ===<br />
<br />
While examining the code, we need to get familiar with documentation. GNU Radio uses Doxygen (the software) for the [http://gnuradio.org/doc/doxygen/ GNU Radio Manual]. The easiest way to go through the documentation is to go through the functions that we use so let us simplify our code by only including the bare bones needed to run the dial-tone example.<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env Python3<br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, "")<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass<br />
<br />
</syntaxhighlight><br />
<br />
Let us examine this line by line:<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
</syntaxhighlight><br />
Tells the shell that this file is a Python file and to use the Python interpreter to run this file. Should always be included at the top to run from the terminal.<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
</syntaxhighlight><br />
<br />
Tells Python the modules to include. We must always have '''gr''' to run GNU Radio applications. The audio sink is included in the audio module and the signal_source_f is included in the analog module which is why we include them. [http://legacy.python.org/dev/peps/pep-0008/ PEP8] tells us we should import every module on its own line.<br />
<br />
<syntaxhighlight lang="python"><br />
class my_top_block(gr.top_block):<br />
</syntaxhighlight><br />
Define a class called "my_top_block" which is derived from another class, '''gr.top_block'''. This class is basically a container for the flow graph. By deriving from gr.top_block, we get all the hooks and functions we need to add blocks and interconnect them.<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self):<br />
</syntaxhighlight><br />
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.<br />
<br />
<syntaxhighlight lang="python"><br />
gr.top_block.__init__(self)<br />
</syntaxhighlight><br />
The parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br />
<br />
<syntaxhighlight lang="python"><br />
sample_rate = 32000<br />
ampl = 0.1<br />
</syntaxhighlight><br />
Variable declarations for sampling rate and amplitude that we will later use.<br />
<br />
=== 3.1.3. A Look at Documentation ===<br />
<br />
<syntaxhighlight lang="python"><br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
</syntaxhighlight><br />
Here we are using functions from GNU Radio so let's have a look at the documentation for '''analog.sig_source_f''' which is available in [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html the GNU Radio manual]. We can find it easily by using the search function as below:<br />
<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_search.png<br />
<br />
We can then scroll down to '''Member Function Documentation''' to see how the function is used and the parameters it accepts as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_source.png<br />
<br />
We can see that our function '''analog.sig_source_f''' takes in 5 parameters but in our code we are only using 4. There is no error because the last input '''offset''' is set to "0" by default as shown in the documentation. The first input is the '''sampling_freq''' which we defined as '''sample_rate''' in our code. The second input is asking for a '''gr::analog::gr_waveform_t''' waveform so let's click that [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177 link] to find out more.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_waveform.png<br />
<br />
We can see that there are a couple of options to choose from. In this case we chose '''analog.GR_SIN_WAVE'''. The third input is '''wave_freq''' which we input "350" or "440". The fourth input is '''ampl''' which we defined as '''ampl'''.<br />
<br />
<syntaxhighlight lang="python"><br />
dst = audio.sink(sample_rate, "")<br />
</syntaxhighlight><br />
Because documentation is so important, let's look at another example. Again, we can look at the documentation for '''audio.sink''' which is available on the GNU Radio Manual through the search function:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink.png<br />
<br />
We can then as before scroll down to the '''Member Function Documentation''' to see the parameters it accepts:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink_detail.png<br />
<br />
This time we have 3 inputs with the last being optional. In our code, for the first input '''sampling_rate''' we used are '''sample_rate''' variable. In the second input, we have a choice for what device to use for audio output. If we leave it alone as "" then it'll choose the default on our machine.<br />
<br />
=== 3.1.4. Connecting the Block Together ===<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
</syntaxhighlight><br />
The general syntax for connecting blocks is self.connect(block1, block2, block3, ...) which would connect the output of block1 with the input of block2, the output of block2 with the input of block3 and so on. We can connect as many blocks as we wish with one connect() call. However this only work when there is a one-to-one correspondence. If we go back to our initial flowgraph, there are 2 inputs to the '''Audio Sink''' block. The way to connect them is by using the syntax above. The first line connects the only output of src0 (350 Hz waveform) to the first input of dst (Audio Sink). The second line connects the only output of src1 (440 Hz waveform) to the second input of dst (Audio Sink). The code so far is equivalent to the flowgraph we have created in the beginning; the rest of the lines simply start the flowgraph and provide a keyboard interrupt.<br />
<br />
<syntaxhighlight lang="python"><br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except KeyboardInterrupt:<br />
pass<br />
</syntaxhighlight><br />
<br />
Luckily we are past the early years of GNU Radio when there was no GRC to make the Python files for us. Nowadays we can simply click things together in GRC instead of having to write code in Python to build flowgraphs. Still, a good understanding of what is going on every time we run GRC is good to know as it gives us more control of what we want the program to do.<br />
<br />
=== 3.1.5. Modifying the GRC Generated Python File ===<br />
<br />
For instance, what if we wanted to change variables such as frequency and amplitude when a certain event occurs. How do we implement if statements, state machines, etc in GNU Radio? One way is to create our own blocks which we will delve into at length later. Another is to modify our GRC generated python file.<br />
<br />
Our friend heard we were into RF so being cheap he has asked us to power his house using RF. He wants us to give him high power during the day so he can watch TV and play video games while at night give him low power so he power his alarm clock to wake up for work in the morning. We first need to setup the clock which keeps track of the day and gives us 1 for day or 0 for night. Once we get the time we can send him power through a sine wave using our massive terawatt amplifier and massive dish in our backyard. We did the calculations and we want to pulse at 1kHz, 1 amplitude during the day and 100Hz, 0.3 amplitude at night. Here's what we came up with in GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else.png<br />
<br />
<span style="color:blue">- '''Frequency''' to "freq", '''Amplitude''' to "ampl"</span><br /><br />
<span style="color:red">- '''ID''' to "probe"</span><br /><br />
- Everything else is visible<br />
<br />
The top section keeps track of time and will switch from 0 to 1 while the bottom section sends the pulse. The problem we encounter however is that there is no if-statement block. Sure we can tie the probe to the frequency as we did in tutorial2 for the singing sine wave but that only allows changing by a factor. What if we wanted to change multiple things and not by a linear factor? Let's start by running the flowgraph to make sure we get the output as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else_output.png<br />
<br />
Now we can open up the GRC generated python file if_else.py which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.get_number()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We are only concerned about a couple of parts namely the part where the probe is being read:<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
We can see that the variable '''val''' is obtaining the value of the probe block. We can write our if-else statement here based on the value of '''val''' to change the amplitude and frequency of our sine wave. But how do we change the frequency and amplitude? We can use the part where the '''QT GUI Entry''' updates the flowgraph. For the variable freq:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
</syntaxhighlight><br />
<br />
and for the variable ampl:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
</syntaxhighlight><br />
We can see that the functions set_ampl and set_freq can be used for just that - setting the amplitude and the frequency. Thus we can go back and modify our probe function with the if-else statement to give power to our friend.<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_funct ion_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
<br />
Now there is one more thing we need to take care of. GRC has compiled the python file in the order of creation of the elements, which was okay as long as there were no crossreferences. With the introduced adaptation (calling set_ampl and set_freq inside the _variable_function_probe_0_probe()) we need to fix the order of declarations. As set_ampl and set_freq both modify parameters of analog_sig_source_x_0_0 but analog_sig_source_x_0_0 is not instantiated before line 150, we have to move the declaration of the _variable_function_probe_0_probe() and everything related below that.<br />
<br />
<syntaxhighlight lang="python"><br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
</syntaxhighlight><br />
<br />
Full code copied below:<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print (val)<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We can then simply run our flowgraph from outside of GRC using<br />
<br />
<pre><br />
$ python3 if_else_mod.py<br />
</pre><br />
We should be able to see the numbers 0 and 1 on the terminal and the sine wave changing amplitude and frequency as the numbers change.<br />
<br />
This tutorial is merely an introduction on using python in GNU Radio, for a more advanced tutorial see [[TutorialsWritePythonApplications]] .<br />
<br />
== 3.2. Where Do Blocks Come From? ==<br />
<br />
Now that we have covered some of the ways we can modify our GRC generated Python files, we can see how to make our own blocks to add functionality to suit our specific project needs. Let us start off simple to get a feel for the waters. We will be making a block that is able to multiply a signal by the number we specify. The first thing we need to realize is that GNU Radio comes with gr_modtool, a utility that makes creating OOT Modules easy. Let us open up a terminal and begin in a project folder for the tutorial three.<br />
<br />
=== 3.2.1. Using gr_modtool ===<br />
<br />
Before we begin, we need to figure out what the commands for gr_modtool are so let's ask for help.<br />
<br />
<pre>$ gr_modtool --help<br />
Usage: gr_modtool [OPTIONS] COMMAND [ARGS]...<br />
<br />
A tool for editing GNU Radio out-of-tree modules.<br />
<br />
Options:<br />
--help Show this message and exit.<br />
<br />
Commands:<br />
add Adds a block to the out-of-tree module.<br />
disable Disable selected block in module.<br />
info Return information about a given module<br />
makeyaml Generate YAML files for GRC block bindings.<br />
newmod Create new empty module, use add to add blocks.<br />
rename Rename a block inside a module.<br />
rm Remove a block from a module.<br />
update Update the grc bindings for a block<br />
<br />
Manipulate with GNU Radio modules source code tree. Call it without<br />
options to run specified command interactively<br />
<br />
</pre><br />
We immediately see there are many commands available. In this tutorial we will only cover '''newmod''' and '''add'''; however, the thorough explanation should enable smooth usage of the other gr_modtool commands without guidance.<br />
<br />
First, we notice that in addition to getting help seeing the commands we can use, we can also request more information on individual commands. Let us start with newmod as that is the command to create a new out-of-tree module.<br />
<br />
<pre><br />
$ gr_modtool newmod --help<br />
Usage: gr_modtool newmod [OPTIONS] MODULE-NAME<br />
<br />
Create a new out-of-tree module<br />
<br />
The argument MODULE-NAME is the name of the module to be added.<br />
<br />
Options:<br />
--srcdir TEXT Source directory for the module template.<br />
-d, --directory TEXT Base directory of the module. Defaults to the cwd.<br />
--skip-lib Don't do anything in the lib/ subdirectory.<br />
--skip-swig Don't do anything in the swig/ subdirectory.<br />
--skip-python Don't do anything in the python/ subdirectory.<br />
--skip-grc Don't do anything in the grc/ subdirectory.<br />
--scm-mode [yes|no|auto] Use source control management [ yes | no | auto ]).<br />
-y, --yes Answer all questions with 'yes'. This can<br />
overwrite and delete your files, so be careful.<br />
--help Show this message and exit.<br />
<br />
</pre><br />
Now that we have read over the list of commands for newmod, we can deduce that the one we want to pick is -n which is the default so we can simply type the MODULE_NAME after newmod. It is actually advised to avoid using "-n" as for other commands it can override the auto-detected name. For now, let's ignore the other options.<br />
<br />
=== 3.2.2. Setting up a new block ===<br />
<br />
<pre><br />
$ gr_modtool newmod tutorial<br />
Creating out-of-tree module in ./gr-tutorial... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.<br />
</pre><br />
We should now see a new folder, gr-tutorial, in our current directory. Let's examine the folder to figure out what gr_modtool has done for us.<br />
<br />
<pre><br />
gr-tutorial$ ls<br />
apps cmake CMakeLists.txt docs examples grc include lib Python swig<br />
</pre><br />
Since we are dealing with Python in this tutorial we only need to concern ourselves with the Python folder and the grc folder. Before we can dive into code, we need to create a block from a template. There are actually four different types of Python blocks, however it's a little too soon to discuss that. We will dive into the synchronous 1:1 input to output block to make explaining things easier (this is a block that outputs as many items as it gets as input, but don't worry about this right now).<br />
<br />
Now we know the language we want to write our block in (Python) and the type of block (synchronous block) we can now add a block to our module. Again, we should run the gr_modtool help command until we are familiar with the different commands. We see that the '''add''' command is what we are looking for. Now we need to run help on the add command in order to see what we need to enter given the information we have so far.<br />
<br />
<pre><br />
gr-tutorial$ gr_modtool help add<br />
... (General Options from Last Help)<br />
Add module options:<br />
-t BLOCK_TYPE, --block-type=BLOCK_TYPE<br />
One of sink, source, sync, decimator, interpolator,<br />
general, tagged_stream, hier, noblock.<br />
--license-file=LICENSE_FILE<br />
File containing the license header for every source<br />
code file.<br />
--argument-list=ARGUMENT_LIST<br />
The argument list for the constructor and make<br />
functions.<br />
--add-Python-qa If given, Python QA code is automatically added if<br />
possible.<br />
--add-cpp-qa If given, C++ QA code is automatically added if<br />
possible.<br />
--skip-cmakefiles If given, only source files are written, but<br />
CMakeLists.txt files are left unchanged.<br />
-l LANG, --lang=LANG<br />
Language (cpp or Python)<br />
</pre><br />
We can see the '''-l LANG''' and the '''-t BLOCK_TYPE''' are relevant for our example. Thus when creating our new block, we know the command. When prompted for a name simply enter "multiply_py_ff", when prompted for an argument list enter "multiple", and when prompted for Python QA (Quality Assurance) code type "y", or just hit enter (the capital letter is the default value).<br />
<br />
<pre>gr-tutorial$ gr_modtool add -t sync -l python<br />
GNU Radio module name identified: tutorial<br />
Language: Python<br />
Enter name of block/code (without module name prefix): multiply_py_ff<br />
Block/code identifier: multiply_py_ff<br />
Enter valid argument list, including default arguments: multiple<br />
Add Python QA code? [Y/n] y<br />
Adding file 'Python/multiply_py_ff.py'...<br />
Adding file 'Python/qa_multiply_py_ff.py'...<br />
Editing Python/CMakeLists.txt...<br />
Adding file 'grc/tutorial_multiply_py_ff.yml'...<br />
Editing grc/CMakeLists.txt...<br />
</pre><br />
We notice 5 changes: Two changes to CMakeLists.txt files, one new file <code>qa_multiply_py_ff.py</code> which is used to test our code, one new file <code>multiply_py_ff.py</code> which is the functional part, and one new file <code>tutorial_multiply_py_ff.xml</code>, which is used to link the block to the GRC. Again all this happens in the Python and grc subfolders.<br />
<br />
==== 3.2.2.1. What's with the <code>_ff</code>? ====<br />
<br />
For blocks with strict types, we use suffixes to declare the input and output types. This block operates on floats, so we give it the suffix <code>_ff</code>: Float in, float out. Other suffixes are <code>_cc</code> (complex in, complex out), or simply <code>_f</code> (a sink or source with no in- or outputs that uses floats). For a more detailed description, see the [[FAQ]] or the [[BlocksCodingGuide]].<br />
<br />
=== 3.2.3. Modifying the Python Block File ===<br />
<br />
Let's begin with the multiply_py_ff.py file found in the Python folder. Opening it without any changes gives the following:<br />
<br />
<syntaxhighlight lang="python"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class multiply_py_ff(gr.sync_block):<br />
"""<br />
docstring for block multiply_py_ff<br />
"""<br />
def __init__(self, multiple):<br />
gr.sync_block.__init__(self,<br />
name="multiply_py_ff",<br />
in_sig=[<+numpy.float+>],<br />
out_sig=[<+numpy.float+>])<br />
self.multiple = multiple<br />
<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
Let's take this one line by line as our first Python examples. We are already familiar with the imports so we will skip those lines. We are familiar with the constructor (init) of Python so can immediately see that if we want to use our variable "multiple", we need to add another line. Let us not forget to preserve those spaces as some code editors like to add tabs to new lines. How do we use the variable multiple?<br />
<br />
How to use variable multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, multiple):<br />
self.multiple = multiple<br />
gr.sync_block.__init__(self,<br />
</syntaxhighlight><br />
<br />
<br />
We notice that there are "&lt;''...''&gt;" scattered in many places. These placeholders are from gr_modtool and tell us where we need to alter things<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[<+numpy.float+>]<br />
out_sig=[<+numpy.float+>]<br />
</syntaxhighlight><br />
The '''gr.sync_block.''init''''' takes in 4 inputs: self, name, and the size/type of the input and output vectors. First, we want to make the item size a single precision float or numpy.float32 by removing the "&lt;''" and the "''&gt;". If we wanted vectors, we could define those as in_sig=[(numpy.float32,4),numpy.float32]. This means there are two input ports, one for vectors of 4 floats and the other for scalars. It is worth noting that if in_sig contains nothing then it becomes a source block, and if out_sig contains nothing it becomes a sink block (provided we change return <code>len(output_items[0])</code> to return <code>len(input_items[0])</code> since output_items is empty). Our changes to the first placeholders should appear as follows:<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[numpy.float32]<br />
out_sig=[numpy.float32]<br />
</syntaxhighlight><br />
The other piece of code that has the placeholders is in the work function but let us first get a better understanding of the work function:<br />
<br />
<syntaxhighlight lang="python"><br />
def work(self, input_items, output_items)<br />
</syntaxhighlight><br />
The work function is where the actual processing happens, where we want our code to be. Because this is a sync block, the number of input items always equals the number of output items because synchronous block ensures a fixed output to input rate. There are also decimation and interpolation blocks where the number of output items are a user specified multiple of the number of input items. We will further discuss when to use what block type in the third section of this tutorial. For now we look at the placeholder:<br />
<br />
<syntaxhighlight lang="python"><br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
The "in0" and "out" simply store the input and output in a variable to make the block easier to write. The signal processing can be anything including if statements, loops, function calls but for this example we only need to modify the out[:] = in0 line so that our input signal is multiplied by our variable multiple. What do we need to add to make the in0 multiply by our multiple?<br />
<br />
How to Multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
out[:] = in0*self.multiple<br />
</syntaxhighlight><br />
<br />
That's it! Our block should now be able to multiply but to be sure we have these things called Quality Assurance tests!<br />
<br />
=== 3.2.4. QA Tests ===<br />
<br />
Now we need to test it to make sure it will run correctly when we install it unto GNU Radio. This is a very important step and we should never forget to include tests with our code! Let us open up qa_multiply_py_ff.py which is copied below:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
from multiply_py_ff import multiply_py_ff<br />
<br />
class qa_multiply_py_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
# set up fg<br />
self.tb.run ()<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_multiply_py_ff)<br />
</syntaxhighlight><br />
gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. The only part we need to worry about is the def test_001_t function. We know we need input data so let us create data. We want it to be in the form of a vector so that we can test multiple values at once. Let us create a vector of floats<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
</syntaxhighlight><br />
We also need output data so we can compare the input of the block to ensure that it is doing what we expect it to do. Let us multiply by 2 for simplicity.<br />
<br />
<syntaxhighlight lang="python">expected_result = (0, 2, -4, 11, -1)<br />
</syntaxhighlight><br />
Now we can create a flowgraph as we have when we first introduced using Python in GNU Radio. We can use the blocks library specifically the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__source__f.html vector_source_f] function and the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__sink__f.html vector_sink_f] function which are linked to the doxygen manual which we should be able to read and understand. Let us assign three variables "src", "mult", and "snk" to the blocks. The first is shown below:<br />
<br />
<syntaxhighlight lang="python">src = blocks.vector_source_f(src_data)</syntaxhighlight><br />
The rest are hidden below as an exercise:<br />
<br />
What do we assign snk and mult?<br />
<br />
<syntaxhighlight lang="python">mult = multiply_py_ff(2)<br />
snk = blocks.vector_sink_f()<br />
</syntaxhighlight><br />
<br />
<br />
Now we need to connect everything as src <s>&gt; mult</s>&gt; snk. Instead of using self.connect as we did in our other blocks we need to use self.tb.connect because of the setUp function. Below is how we would connect the src block to the mult block.<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (src, mult)<br />
</syntaxhighlight><br />
<br />
How would we connect the other blocks together?<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (mult, snk)<br />
</syntaxhighlight><br />
<br />
<br />
Then we can run the graph and store the data from the sink as below:<br />
<br />
<syntaxhighlight lang="python">self.tb.run ()<br />
result_data = snk.data ()<br />
</syntaxhighlight><br />
Lastly we can run our comparison function that will tell us whether the numbers match up to 6 decimal places. We are using the assertFloatTuplesAlmostEqual instead of the "regular assert functions"https://docs.python.org/2/library/unittest.html#assert-methods included in python's unittest because there may be situations where we cannot get a=b due to rounding in floating point numbers.<br />
<br />
<syntaxhighlight lang="python"><br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
All together the new test_001_t function should appear as below:<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
expected_result = (0, 2, -4, 11, -1)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
snk = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, snk)<br />
self.tb.run ()<br />
result_data = snk.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
<br />
We can then go to the python directory and run:<br />
<br />
<pre><br />
gr-tutorial/python$ python qa_multiply_py_ff.py<br />
.<br />
----------------------------------------------------------------------<br />
Ran 1 test in 0.004s<br />
<br />
OK<br />
</pre><br />
<br />
While we are here, we should take a break to change one of the numbers in the src_data to ensure that the block is actually checking the values and to simply see what an error looks like. Python allows for really quick testing of blocks without having to compile anything; simply change something and re-run the QA test.<br />
<br />
=== 3.2.5. XML Files ===<br />
<br />
At this point we should have written a Python block and a QA test for that block. The next thing to do is edit the XML file in the grc folder so that we can get another step closer to using it in GRC. GRC uses the XML files for all the options we see. We actually don't need to write any Python or C++ code to have a block display in GRC but of course if we would connect it, it wouldn't do anything but give errors. We should get out of the python folder and go to the grc folder where all the XML files reside. There is a tool in gr_modtool called makexml but it is only available for C++ blocks. Let us open the tutorial_multiply_py_ff.xml file copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>multiply_py_ff</name><br />
<key>tutorial_multiply_py_ff</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.multiply_py_ff($multiple)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>...</name><br />
<key>...</key><br />
<type>...</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<source><br />
<name>out</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</source><br />
</block></syntaxhighlight><br />
<br />
We can change the name that appears and the category it will appear in GRC. The category is where the block will be found in GRC. Examples of categories tag are '''Audio''' and '''Waveform Generators''' used in previous examples. Examples of names tag are the '''QT GUI Time Sink''' or the '''Audio Sink'''. Again, we can go through the file and find the modtool place holders. The first is copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
</syntaxhighlight><br />
This is referring to the parameter that we used in the very beginning when creating our block: the variable called "multiple". We can fill it in as below:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<param><br />
<name>Multiple</name><br />
<key>multiple</key><br />
<type>float</type><br />
</param><br />
</syntaxhighlight><br />
The next placeholder can be found in the sink and source tags:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
</syntaxhighlight><br />
We can see that it is asking for a type so we can simply erase everything in the tag and replace it with "float" for both the source and the sink blocks. That should do it for this block. The best way to get more experience writing xml files is to look at the source code of previously made blocks such as the existing multiple block. Let's go back to this and use the documentation tag!<br />
<br />
=== 3.2.6. Installing Python Blocks ===<br />
<br />
Now that we have edited the XML file; we are ready to install the block into the GRC. First, we need to get out of the /grc directory and create a build directory called "build". Inside the build directory, we can then run a series of commands:<br />
<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
<br />
We should then open up the GRC and take a look at the new block.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_blockname.png<br />
<br />
We can see the category and the block name. When we drag it into the GRC workspace, we can see the multiple variable we set in the param tag of the XML file.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_param.png<br />
<br />
Now that we can see our block, let us test it to make sure it works. Below is an example of one of the many ways to check whether it is actually multiplying.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_2.png<br />
<br />
== 3.3. My QPSK Demodulator for Satellites ==<br />
<br />
Now that we have a better understanding on how to add new blocks to GNU Radio for use in the GRC, we can do another example. This time we will be creating a QPSK demodulator block using the same process as before in the same module. Let's first setup the scenario. There is a "noise source" that outputs data in complex float format but let's pretend it comes from a satellite being aimed at our computer. Our secret agent insider tells us this particular satellite encodes digital data using QPSK modulation. We can decode this using a QPSK demodulator that outputs data into bytes. Our insider tells us the space manual doesn't specify whether it's gray code or not. We want to read the bytes using a time sink. What would our flowgraph look like?<br />
<br />
Incomplete Flowgraph...<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_3.png<br />
<br />
<br />
Now that we know the input type, output type, and parameters, we can ask the question we skipped with our multiply_py_ff block. What type of block do we want?<br />
<br />
=== 3.3.1. Choosing a Block Type ===<br />
<br />
The GNU Radio scheduler optimizes for throughput (as opposed to latency). To do this it needs to know the number of samples each block is inputting and outputting thus came the creation of decimation and interpolation blocks where we specify the multiple factor. This is directly related to the sampling rate discussion we had in tutorial 2 where these special types of blocks are able to change the sampling rate to something else in order to comply with a specific piece of hardware such as our soundcard or a specific modulation/demodulation.<br />
<br />
* Synchronous (1:1) - Number of items at input port equals the number of items at output port. An example is the multiply_py_ff block. <br />
* Decimation (N:1) - Number of input items is a fixed multiple of the number of output items. Examples include filters such as decimating high pass and low pass filters (e.g., for sampling rate conversion).<br />
* Interpolation (1:M) - Number of output items is a fixed multiple of the number of input items. An example is the Interpolating FIR Filter.<br />
* General/Basic (N:M) - Provides no relation between the number of input items and the number of output items. An example is the rational resampler block which can be either a sync, decimator, or interpolator.<br />
<br />
When we insert data into our block, do we need more samples or less samples? Or put another way, should our sample rate change?<br />
<br />
What Type of Block Should we Use?<br />
* Sync Block or Basic Block<br />
<br />
<br />
=== 3.3.2. Adding Another Block to our OOT Module ===<br />
<br />
Now we know everything we need to know to create the block in gr_modtool. As a refresher, what would our gr_modtool command be?<br />
<br />
gr_modtool command...<br />
<pre><br />
gr-tutorial$ gr_modtool add -t sync -l python<br />
<br />
name: qpsk_demod_py_cb (c for complex input and b for byte/char/int8 output)<br /><br />
args: gray_code<br /><br />
QA Code: y<br />
</pre><br />
<br />
<br />
Now we have our files setup so we can begin with editing the qpsk_demod_py.py file in the /python folder<br />
<br />
=== 3.3.3. The QPSK Demodulator ===<br />
<br />
The code here is a little more than we have done before so let's take it in steps. First what do we expect our block to do? We can start with a constellation plot which shows the four different quadrants and their mappings to bit 0 and bit 1<br />
<br />
With [http://en.wikipedia.org/wiki/Phase-shift_keying#Quadrature_phase-shift_keying_.28QPSK.29 Gray coding] (adjacent only flips by 1). Note that the mapping we use here is different from the mapping on wikipedia:<br />
<br />
<br />
(-1+1j) 10 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 11 | 01 (1-1j)<br />
<br />
Without Gray coding:<br />
<br />
(-1+1j) 11 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 10 | 01 (1-1j)<br />
<br />
We can see that we will need to write an if-else statement to take into account the gray_code variable. We will also need four other if-else statements inside the main if-else so that we can pick the mapping. Our pseudocode will look as follows:<br />
<br />
if gray_code<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "11" = 3<br />
else<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "10" = 2<br />
<br />
So we have everything we need to implement. Let's go ahead and fill in our gr_modtoool placeholders. We can begin with def init. There are three changes. How do we use the variable gray_code outside the function (similar to what we did with multiple in the last example)? What are the input and output types in [http://docs.scipy.org/doc/numpy/user/basics.types.html? numpy]<br />
<br />
Changes to def ''init''...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
</syntaxhighlight><br />
<br />
Once we have our constructor setup, we can go onto the work function. For simplicity and beauty, let us call the pseudocode we made above a function "get_minimum_distance" that takes samples as input arguments. In our multiply_py_ff example, we took all the samples and multiplied them with with out[:] = in0*self.multiple. The in0 is actually a vector so contains many samples within it. The multiply example required the same operation for each sample so it was okay to simply operate on the entire vector but now we need to have different operations per sample so what do we do?<br />
<br />
How can we operate on samples in a vector?<br />
* loops!<br />
<br />
<syntaxhighlight lang="python"><br />
for i in range(0, len(output_items[0])):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
</syntaxhighlight><br />
<br />
<br />
Now we can move onto the get_minimum_distances(self, sample) function. We already have pseudo code so the next step is translating to Python. Below is a snip of what the code can look like. Again there are multiple ways to do this<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
</syntaxhighlight><br />
Let us try to fill in the other cases for gray code and non-gray code. Below is what the entire file Python file can look like once complete:<br />
<br />
qpsk_demod_py_cb.py<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class qpsk_demod_py(gr.sync_block):<br />
"""<br />
docstring for block qpsk_demod_py<br />
"""<br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
<br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 3 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
else:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 3 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 2 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
<br />
for i in range(0, len(in0)):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
<br />
<br />
Now that we have code, we know what's next!<br />
<br />
=== 3.3.4. Multiple QA Tests ===<br />
<br />
We can test our qpsk_demod_py for when it is in gray_code mode and when it's not in gray_code mode. To do that we need to setup multiple tests in our single QA file. QA tests generally follow the same setup from before. We select some inputs as tests and we check them against what we expect the outputs to be. The only difference from the multiply qa test is that this qa test requires more cases. There are four quadrants we need to map and two modes so in total there are eight test cases. We can open up our qa_qpsk_demod_py_ff.py file to change things.<br />
<br />
We can copy the def test_001_t from the qa_multiply_py_ff code which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def test_001_t (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (-6, 8, -11, 4, 6)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
This time we are working with a complex input so our src = blocks.vector_source_f must change. If we use the search bar in the manual we can find the other options:<br />
<br />
PIC of SEARCH<br />
<br />
b - bytes/unsigned char/int8<br /><br />
c - complex<br /><br />
f - float<br /><br />
i - int<br /><br />
s - short<br />
<br />
What do we change our source and sink vectors to?<br /><br />
src = blocks.vector_source_c (src_data)<br /><br />
dst = blocks.vector_sink_b ()<br /><br />
<br />
<br />
Before we move onto actual test cases, let us decide which mode we are testing for the test_001_t. We can create a new variable and assign it False (translates to 0) to test non-Gray code<br />
<br />
<syntaxhighlight lang="python"><br />
gray_code = False<br />
</syntaxhighlight><br />
Once we know we want to test non gray_code mappings, we can refer to our chart above and start placing in the proper inputs and outputs into the src_data and the expected_results. For instance if we were testing only two cases for non gray_code, we would do:<br />
<br />
<syntaxhighlight lang="python" line="line">src_data = ((-1-1j), (-1+1j))<br />
expected_result = (2, 3)<br />
</syntaxhighlight><br />
Last thing to do is call upon our new block in the "qpsk =" line and pass it the gray_code parameter<br />
<br />
qpsk = ?<br />
* qpsk = qpsk_demod_py_cb (gray_code)<br />
<br />
Now that we are done with the non gray_code test, we can simply create another test "def test_002_t (self):" and copy the contents underneath making sure that for this test we set gray_code = True and change the expected_result so it matches gray_code mapping. The full test is copied below:<br />
<br />
Full QA QPSK Demod Code...<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import numpy<br />
from qpsk_demod_py_cb import qpsk_demod_py<br />
<br />
class qa_qpsk_demod (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
gray_code = False<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (2, 3, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
def test_002_t (self):<br />
gray_code = True<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (3, 2, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qpsk_demod_py_cb.xml")<br />
</syntaxhighlight><br />
<br />
<br />
We can then run the test in Python and all should say something similar to:<br />
<br />
Ran 2 tests in 0.005s<br />
<br />
OK<br />
<br />
So once we verify it works as expected, we can then edit our XML file so that it is usable inside GRC.<br />
<br />
=== 3.3.5. XML Mods, Installation, and Running ===<br />
<br />
This XML is very similar to the XML file for the multiply_py_ff block so all we need to do is set the gray_code parameter and pick the correct input (complex) and output (byte) types. A copy of the full XML file is below:<br />
<br />
XML File for QPSK Demod<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>qpsk_demod_py</name><br />
<key>tutorial_qpsk_demod_py</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.qpsk_demod_py($gray_code)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<type>int</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<!-- e.g. int, float, complex, byte, short, xxx_vector, ...--><br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</block><br />
<br />
</syntaxhighlight><br />
<br />
<br />
We can then install as we did for the multiply block however we need to rerun cmake in order to take into account the new block:<br />
<pre><br />
cd build<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
</pre><br />
Then we can open up our GRC file from the beginning and place our missing block we just made.<br />
<br />
What is the Expected Output?<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_4.png<br />
<br />
== 3.4. Conclusion ==<br />
<br />
And that is it for now with Python. Let us know your thoughts before going on to the [[Guided_Tutorial_GNU_Radio_in_C++|C++ tutorial]].<br />
<br />
=== 3.4.1. Questions We Should Now be Able to Answer ===<br />
<br />
1. How do we set input- and output signatures in Python blocks?<br /><br />
2. Consider this I/O signature: (FIXME). Which input types and output types does it take?<br />
<br />
=== 3.4.2. Links to Further Information ===<br />
<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/BlocksCodingGuide Blocks Coding Guide]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModules Out-of-Tree Modules]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/TutorialsWritePythonApplications Writing Python Applications]<br />
<br />
== 3.5. Candidates for Future Sections ==<br />
<br />
Possible topics we may want to add depending on feedback and questions on the mailing list<br /><br />
- How to add documentation to blocks<br /><br />
- Constructor arguments, History, Output Multiple<br />
<br />
<br />
-----<br />
<br />
&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GNU_Radio_in_Python&diff=6421Guided Tutorial GNU Radio in Python2019-12-15T08:41:44Z<p>Cmrincon: /* 3.2.2. Setting up a new block */</p>
<hr />
<div>&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
<br />
= Tutorial: Working with GNU Radio in Python =<br />
<br />
== Objectives ==<br />
<br />
* Python Blocks<br />
* OOT modules make the actual apps / functionality (GR is the API!)<br />
* How to add OOTs<br />
* How to add Python blocks with gr_modtool and how to code them<br />
* QPSK mapping<br />
* How to add GRC bindings for block<br />
<br />
== Prerequisites ==<br />
<br />
* Working Installation of GNU Radio 3.8 or later<br />
* [[Guided_Tutorial_GRC|GRC Tutorial]] (Recommended)<br />
* Familiar with Python<br />
<br />
-----<br />
<br />
== 3.1. Intro to Using GNU Radio with Python ==<br />
<br />
This tutorial goes through three parts. The first is how to modify, create, or simply understand the Python generated files GRC produces for us. The second is how to create our own custom out-of-tree (OOT) modules from the ground up. Lastly we go through an actual project to get more practice and build intuition on how we can use GNU Radio in our own project. As with the last tutorial, all the content - pictures, source code, and grc files - is included in the [https://github.com/gnuradio/gr-tutorial gr-tutorial repository] which we should have a local copy if we followed the directions from the [[Guided_Tutorial_GRC|GRC Tutorial]]<br />
<br />
Again we should have a directory with the solutions and a directory with our work as below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
/home/user/gnuradio/tutorials/solutions<br />
/home/user/gnuradio/tutorials/work<br />
</syntaxhighlight><br />
<br />
As a rule, if we hover over the GRC flowgraph images, we will be able to see the corresponding filename. Same applies for other images. Full code files are collapsed with the filename in the collapsed handle.<br />
<br />
=== 3.1.1. GRC Generated Python Files ===<br />
<br />
Let us look at a dial-tone example on the GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png<br />
<br />
When we click the '''Generate''' button, the terminal tells us it produced a .py file so let's open that to examine its code which is reproduced below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: tutorial_three_1<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
from gnuradio.filter import firdes<br />
import sys<br />
import signal<br />
from PyQt5 import Qt<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
<br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("tutorial_three_1")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.samp_rate = samp_rate = 32000<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.audio_sink_0 = audio.sink(samp_rate, '', True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
<br />
<br />
<br />
def main(top_block_cls=tutorial_three_1, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
The first thing for us to realize is that the GRC can generate Python files that we can then modify to do things we wouldn't be able to do in GNU Radio Companion such as perform [[TutorialsSimulations|simulations]]. The libraries available in Python open up a whole new realm of possibilities! For now, we will explore the structure of the GRC Python files so we are comfortable creating more interesting applications.<br />
<br />
=== 3.1.2. Hello World Dissected ===<br />
<br />
While examining the code, we need to get familiar with documentation. GNU Radio uses Doxygen (the software) for the [http://gnuradio.org/doc/doxygen/ GNU Radio Manual]. The easiest way to go through the documentation is to go through the functions that we use so let us simplify our code by only including the bare bones needed to run the dial-tone example.<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env Python3<br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, "")<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass<br />
<br />
</syntaxhighlight><br />
<br />
Let us examine this line by line:<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
</syntaxhighlight><br />
Tells the shell that this file is a Python file and to use the Python interpreter to run this file. Should always be included at the top to run from the terminal.<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
</syntaxhighlight><br />
<br />
Tells Python the modules to include. We must always have '''gr''' to run GNU Radio applications. The audio sink is included in the audio module and the signal_source_f is included in the analog module which is why we include them. [http://legacy.python.org/dev/peps/pep-0008/ PEP8] tells us we should import every module on its own line.<br />
<br />
<syntaxhighlight lang="python"><br />
class my_top_block(gr.top_block):<br />
</syntaxhighlight><br />
Define a class called "my_top_block" which is derived from another class, '''gr.top_block'''. This class is basically a container for the flow graph. By deriving from gr.top_block, we get all the hooks and functions we need to add blocks and interconnect them.<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self):<br />
</syntaxhighlight><br />
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.<br />
<br />
<syntaxhighlight lang="python"><br />
gr.top_block.__init__(self)<br />
</syntaxhighlight><br />
The parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br />
<br />
<syntaxhighlight lang="python"><br />
sample_rate = 32000<br />
ampl = 0.1<br />
</syntaxhighlight><br />
Variable declarations for sampling rate and amplitude that we will later use.<br />
<br />
=== 3.1.3. A Look at Documentation ===<br />
<br />
<syntaxhighlight lang="python"><br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
</syntaxhighlight><br />
Here we are using functions from GNU Radio so let's have a look at the documentation for '''analog.sig_source_f''' which is available in [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html the GNU Radio manual]. We can find it easily by using the search function as below:<br />
<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_search.png<br />
<br />
We can then scroll down to '''Member Function Documentation''' to see how the function is used and the parameters it accepts as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_source.png<br />
<br />
We can see that our function '''analog.sig_source_f''' takes in 5 parameters but in our code we are only using 4. There is no error because the last input '''offset''' is set to "0" by default as shown in the documentation. The first input is the '''sampling_freq''' which we defined as '''sample_rate''' in our code. The second input is asking for a '''gr::analog::gr_waveform_t''' waveform so let's click that [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177 link] to find out more.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_waveform.png<br />
<br />
We can see that there are a couple of options to choose from. In this case we chose '''analog.GR_SIN_WAVE'''. The third input is '''wave_freq''' which we input "350" or "440". The fourth input is '''ampl''' which we defined as '''ampl'''.<br />
<br />
<syntaxhighlight lang="python"><br />
dst = audio.sink(sample_rate, "")<br />
</syntaxhighlight><br />
Because documentation is so important, let's look at another example. Again, we can look at the documentation for '''audio.sink''' which is available on the GNU Radio Manual through the search function:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink.png<br />
<br />
We can then as before scroll down to the '''Member Function Documentation''' to see the parameters it accepts:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink_detail.png<br />
<br />
This time we have 3 inputs with the last being optional. In our code, for the first input '''sampling_rate''' we used are '''sample_rate''' variable. In the second input, we have a choice for what device to use for audio output. If we leave it alone as "" then it'll choose the default on our machine.<br />
<br />
=== 3.1.4. Connecting the Block Together ===<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
</syntaxhighlight><br />
The general syntax for connecting blocks is self.connect(block1, block2, block3, ...) which would connect the output of block1 with the input of block2, the output of block2 with the input of block3 and so on. We can connect as many blocks as we wish with one connect() call. However this only work when there is a one-to-one correspondence. If we go back to our initial flowgraph, there are 2 inputs to the '''Audio Sink''' block. The way to connect them is by using the syntax above. The first line connects the only output of src0 (350 Hz waveform) to the first input of dst (Audio Sink). The second line connects the only output of src1 (440 Hz waveform) to the second input of dst (Audio Sink). The code so far is equivalent to the flowgraph we have created in the beginning; the rest of the lines simply start the flowgraph and provide a keyboard interrupt.<br />
<br />
<syntaxhighlight lang="python"><br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except KeyboardInterrupt:<br />
pass<br />
</syntaxhighlight><br />
<br />
Luckily we are past the early years of GNU Radio when there was no GRC to make the Python files for us. Nowadays we can simply click things together in GRC instead of having to write code in Python to build flowgraphs. Still, a good understanding of what is going on every time we run GRC is good to know as it gives us more control of what we want the program to do.<br />
<br />
=== 3.1.5. Modifying the GRC Generated Python File ===<br />
<br />
For instance, what if we wanted to change variables such as frequency and amplitude when a certain event occurs. How do we implement if statements, state machines, etc in GNU Radio? One way is to create our own blocks which we will delve into at length later. Another is to modify our GRC generated python file.<br />
<br />
Our friend heard we were into RF so being cheap he has asked us to power his house using RF. He wants us to give him high power during the day so he can watch TV and play video games while at night give him low power so he power his alarm clock to wake up for work in the morning. We first need to setup the clock which keeps track of the day and gives us 1 for day or 0 for night. Once we get the time we can send him power through a sine wave using our massive terawatt amplifier and massive dish in our backyard. We did the calculations and we want to pulse at 1kHz, 1 amplitude during the day and 100Hz, 0.3 amplitude at night. Here's what we came up with in GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else.png<br />
<br />
<span style="color:blue">- '''Frequency''' to "freq", '''Amplitude''' to "ampl"</span><br /><br />
<span style="color:red">- '''ID''' to "probe"</span><br /><br />
- Everything else is visible<br />
<br />
The top section keeps track of time and will switch from 0 to 1 while the bottom section sends the pulse. The problem we encounter however is that there is no if-statement block. Sure we can tie the probe to the frequency as we did in tutorial2 for the singing sine wave but that only allows changing by a factor. What if we wanted to change multiple things and not by a linear factor? Let's start by running the flowgraph to make sure we get the output as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else_output.png<br />
<br />
Now we can open up the GRC generated python file if_else.py which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.get_number()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We are only concerned about a couple of parts namely the part where the probe is being read:<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
We can see that the variable '''val''' is obtaining the value of the probe block. We can write our if-else statement here based on the value of '''val''' to change the amplitude and frequency of our sine wave. But how do we change the frequency and amplitude? We can use the part where the '''QT GUI Entry''' updates the flowgraph. For the variable freq:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
</syntaxhighlight><br />
<br />
and for the variable ampl:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
</syntaxhighlight><br />
We can see that the functions set_ampl and set_freq can be used for just that - setting the amplitude and the frequency. Thus we can go back and modify our probe function with the if-else statement to give power to our friend.<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_funct ion_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
<br />
Now there is one more thing we need to take care of. GRC has compiled the python file in the order of creation of the elements, which was okay as long as there were no crossreferences. With the introduced adaptation (calling set_ampl and set_freq inside the _variable_function_probe_0_probe()) we need to fix the order of declarations. As set_ampl and set_freq both modify parameters of analog_sig_source_x_0_0 but analog_sig_source_x_0_0 is not instantiated before line 150, we have to move the declaration of the _variable_function_probe_0_probe() and everything related below that.<br />
<br />
<syntaxhighlight lang="python"><br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
</syntaxhighlight><br />
<br />
Full code copied below:<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print (val)<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We can then simply run our flowgraph from outside of GRC using<br />
<br />
<pre><br />
$ python3 if_else_mod.py<br />
</pre><br />
We should be able to see the numbers 0 and 1 on the terminal and the sine wave changing amplitude and frequency as the numbers change.<br />
<br />
This tutorial is merely an introduction on using python in GNU Radio, for a more advanced tutorial see [[TutorialsWritePythonApplications]] .<br />
<br />
== 3.2. Where Do Blocks Come From? ==<br />
<br />
Now that we have covered some of the ways we can modify our GRC generated Python files, we can see how to make our own blocks to add functionality to suit our specific project needs. Let us start off simple to get a feel for the waters. We will be making a block that is able to multiply a signal by the number we specify. The first thing we need to realize is that GNU Radio comes with gr_modtool, a utility that makes creating OOT Modules easy. Let us open up a terminal and begin in a project folder for the tutorial three.<br />
<br />
=== 3.2.1. Using gr_modtool ===<br />
<br />
Before we begin, we need to figure out what the commands for gr_modtool are so let's ask for help.<br />
<br />
<pre>$ gr_modtool --help<br />
Usage: gr_modtool [OPTIONS] COMMAND [ARGS]...<br />
<br />
A tool for editing GNU Radio out-of-tree modules.<br />
<br />
Options:<br />
--help Show this message and exit.<br />
<br />
Commands:<br />
add Adds a block to the out-of-tree module.<br />
disable Disable selected block in module.<br />
info Return information about a given module<br />
makeyaml Generate YAML files for GRC block bindings.<br />
newmod Create new empty module, use add to add blocks.<br />
rename Rename a block inside a module.<br />
rm Remove a block from a module.<br />
update Update the grc bindings for a block<br />
<br />
Manipulate with GNU Radio modules source code tree. Call it without<br />
options to run specified command interactively<br />
<br />
</pre><br />
We immediately see there are many commands available. In this tutorial we will only cover '''newmod''' and '''add'''; however, the thorough explanation should enable smooth usage of the other gr_modtool commands without guidance.<br />
<br />
First, we notice that in addition to getting help seeing the commands we can use, we can also request more information on individual commands. Let us start with newmod as that is the command to create a new out-of-tree module.<br />
<br />
<pre><br />
$ gr_modtool newmod --help<br />
Usage: gr_modtool newmod [OPTIONS] MODULE-NAME<br />
<br />
Create a new out-of-tree module<br />
<br />
The argument MODULE-NAME is the name of the module to be added.<br />
<br />
Options:<br />
--srcdir TEXT Source directory for the module template.<br />
-d, --directory TEXT Base directory of the module. Defaults to the cwd.<br />
--skip-lib Don't do anything in the lib/ subdirectory.<br />
--skip-swig Don't do anything in the swig/ subdirectory.<br />
--skip-python Don't do anything in the python/ subdirectory.<br />
--skip-grc Don't do anything in the grc/ subdirectory.<br />
--scm-mode [yes|no|auto] Use source control management [ yes | no | auto ]).<br />
-y, --yes Answer all questions with 'yes'. This can<br />
overwrite and delete your files, so be careful.<br />
--help Show this message and exit.<br />
<br />
</pre><br />
Now that we have read over the list of commands for newmod, we can deduce that the one we want to pick is -n which is the default so we can simply type the MODULE_NAME after newmod. It is actually advised to avoid using "-n" as for other commands it can override the auto-detected name. For now, let's ignore the other options.<br />
<br />
=== 3.2.2. Setting up a new block ===<br />
<br />
<pre><br />
$ gr_modtool newmod tutorial<br />
Creating out-of-tree module in ./gr-tutorial... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.<br />
</pre><br />
We should now see a new folder, gr-tutorial, in our current directory. Let's examine the folder to figure out what gr_modtool has done for us.<br />
<br />
<pre><br />
gr-tutorial$ ls<br />
apps cmake CMakeLists.txt docs examples grc include lib Python swig<br />
</pre><br />
Since we are dealing with Python in this tutorial we only need to concern ourselves with the Python folder and the grc folder. Before we can dive into code, we need to create a block from a template. There are actually four different types of Python blocks, however it's a little too soon to discuss that. We will dive into the synchronous 1:1 input to output block to make explaining things easier (this is a block that outputs as many items as it gets as input, but don't worry about this right now).<br />
<br />
Now we know the language we want to write our block in (Python) and the type of block (synchronous block) we can now add a block to our module. Again, we should run the gr_modtool help command until we are familiar with the different commands. We see that the '''add''' command is what we are looking for. Now we need to run help on the add command in order to see what we need to enter given the information we have so far.<br />
<br />
<pre><br />
gr-tutorial$ gr_modtool help add<br />
... (General Options from Last Help)<br />
Add module options:<br />
-t BLOCK_TYPE, --block-type=BLOCK_TYPE<br />
One of sink, source, sync, decimator, interpolator,<br />
general, tagged_stream, hier, noblock.<br />
--license-file=LICENSE_FILE<br />
File containing the license header for every source<br />
code file.<br />
--argument-list=ARGUMENT_LIST<br />
The argument list for the constructor and make<br />
functions.<br />
--add-Python-qa If given, Python QA code is automatically added if<br />
possible.<br />
--add-cpp-qa If given, C++ QA code is automatically added if<br />
possible.<br />
--skip-cmakefiles If given, only source files are written, but<br />
CMakeLists.txt files are left unchanged.<br />
-l LANG, --lang=LANG<br />
Language (cpp or Python)<br />
</pre><br />
We can see the '''-l LANG''' and the '''-t BLOCK_TYPE''' are relevant for our example. Thus when creating our new block, we know the command. When prompted for a name simply enter "multiply_py_ff", when prompted for an argument list enter "multiple", and when prompted for Python QA (Quality Assurance) code type "y", or just hit enter (the capital letter is the default value).<br />
<br />
<pre>gr-tutorial$ gr_modtool add -t sync -l python<br />
GNU Radio module name identified: tutorial<br />
Language: Python<br />
Enter name of block/code (without module name prefix): multiply_py_ff<br />
Block/code identifier: multiply_py_ff<br />
Enter valid argument list, including default arguments: multiple<br />
Add Python QA code? [Y/n] y<br />
Adding file 'Python/multiply_py_ff.py'...<br />
Adding file 'Python/qa_multiply_py_ff.py'...<br />
Editing Python/CMakeLists.txt...<br />
Adding file 'grc/tutorial_multiply_py_ff.yml'...<br />
Editing grc/CMakeLists.txt...<br />
</pre><br />
We notice 5 changes: Two changes to CMakeLists.txt files, one new file <code>qa_multiply_py_ff.py</code> which is used to test our code, one new file <code>multiply_py_ff.py</code> which is the functional part, and one new file <code>tutorial_multiply_py_ff.xml</code>, which is used to link the block to the GRC. Again all this happens in the Python and grc subfolders.<br />
<br />
==== 3.2.2.1. What's with the <code>_ff</code>? ====<br />
<br />
For blocks with strict types, we use suffixes to declare the input and output types. This block operates on floats, so we give it the suffix <code>_ff</code>: Float in, float out. Other suffixes are <code>_cc</code> (complex in, complex out), or simply <code>_f</code> (a sink or source with no in- or outputs that uses floats). For a more detailed description, see the [[FAQ]] or the [[BlocksCodingGuide]].<br />
<br />
=== 3.2.3. Modifying the Python Block File ===<br />
<br />
Let's begin with the multiply_py_ff.py file found in the Python folder. Opening it without any changes gives the following:<br />
<br />
<syntaxhighlight lang="python"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class multiply_py_ff(gr.sync_block):<br />
"""<br />
docstring for block multiply_py_ff<br />
"""<br />
def __init__(self, multiple):<br />
gr.sync_block.__init__(self,<br />
name="multiply_py_ff",<br />
in_sig=[<+numpy.float+>],<br />
out_sig=[<+numpy.float+>])<br />
self.multiple = multiple<br />
<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
Let's take this one line by line as our first Python examples. We are already familiar with the imports so we will skip those lines. We are familiar with the constructor (init) of Python so can immediately see that if we want to use our variable "multiple", we need to add another line. Let us not forget to preserve those spaces as some code editors like to add tabs to new lines. How do we use the variable multiple?<br />
<br />
How to use variable multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, multiple):<br />
self.multiple = multiple<br />
gr.sync_block.__init__(self,<br />
</syntaxhighlight><br />
<br />
<br />
We notice that there are "&lt;''...''&gt;" scattered in many places. These placeholders are from gr_modtool and tell us where we need to alter things<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[<+numpy.float+>]<br />
out_sig=[<+numpy.float+>]<br />
</syntaxhighlight><br />
The '''gr.sync_block.''init''''' takes in 4 inputs: self, name, and the size/type of the input and output vectors. First, we want to make the item size a single precision float or numpy.float32 by removing the "&lt;''" and the "''&gt;". If we wanted vectors, we could define those as in_sig=[(numpy.float32,4),numpy.float32]. This means there are two input ports, one for vectors of 4 floats and the other for scalars. It is worth noting that if in_sig contains nothing then it becomes a source block, and if out_sig contains nothing it becomes a sink block (provided we change return <code>len(output_items[0])</code> to return <code>len(input_items[0])</code> since output_items is empty). Our changes to the first placeholders should appear as follows:<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[numpy.float32]<br />
out_sig=[numpy.float32]<br />
</syntaxhighlight><br />
The other piece of code that has the placeholders is in the work function but let us first get a better understanding of the work function:<br />
<br />
<syntaxhighlight lang="python"><br />
def work(self, input_items, output_items)<br />
</syntaxhighlight><br />
The work function is where the actual processing happens, where we want our code to be. Because this is a sync block, the number of input items always equals the number of output items because synchronous block ensures a fixed output to input rate. There are also decimation and interpolation blocks where the number of output items are a user specified multiple of the number of input items. We will further discuss when to use what block type in the third section of this tutorial. For now we look at the placeholder:<br />
<br />
<syntaxhighlight lang="python"><br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
The "in0" and "out" simply store the input and output in a variable to make the block easier to write. The signal processing can be anything including if statements, loops, function calls but for this example we only need to modify the out[:] = in0 line so that our input signal is multiplied by our variable multiple. What do we need to add to make the in0 multiply by our multiple?<br />
<br />
How to Multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
out[:] = in0*self.multiple<br />
</syntaxhighlight><br />
<br />
That's it! Our block should now be able to multiply but to be sure we have these things called Quality Assurance tests!<br />
<br />
=== 3.2.4. QA Tests ===<br />
<br />
Now we need to test it to make sure it will run correctly when we install it unto GNU Radio. This is a very important step and we should never forget to include tests with our code! Let us open up qa_multiply_py_ff.py which is copied below:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
from multiply_py_ff import multiply_py_ff<br />
<br />
class qa_multiply_py_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
# set up fg<br />
self.tb.run ()<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_multiply_py_ff, "qa_multiply_py_ff.xml")<br />
</syntaxhighlight><br />
gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. The only part we need to worry about is the def test_001_t function. We know we need input data so let us create data. We want it to be in the form of a vector so that we can test multiple values at once. Let us create a vector of floats<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
</syntaxhighlight><br />
We also need output data so we can compare the input of the block to ensure that it is doing what we expect it to do. Let us multiply by 2 for simplicity.<br />
<br />
<syntaxhighlight lang="python">expected_result = (0, 2, -4, 11, -1)<br />
</syntaxhighlight><br />
Now we can create a flowgraph as we have when we first introduced using Python in GNU Radio. We can use the blocks library specifically the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__source__f.html vector_source_f] function and the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__sink__f.html vector_sink_f] function which are linked to the doxygen manual which we should be able to read and understand. Let us assign three variables "src", "mult", and "snk" to the blocks. The first is shown below:<br />
<br />
<syntaxhighlight lang="python">src = blocks.vector_source_f(src_data)</syntaxhighlight><br />
The rest are hidden below as an exercise:<br />
<br />
What do we assign snk and mult?<br />
<br />
<syntaxhighlight lang="python">mult = multiply_py_ff(2)<br />
snk = blocks.vector_sink_f()<br />
</syntaxhighlight><br />
<br />
<br />
Now we need to connect everything as src <s>&gt; mult</s>&gt; snk. Instead of using self.connect as we did in our other blocks we need to use self.tb.connect because of the setUp function. Below is how we would connect the src block to the mult block.<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (src, mult)<br />
</syntaxhighlight><br />
<br />
How would we connect the other blocks together?<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (mult, snk)<br />
</syntaxhighlight><br />
<br />
<br />
Then we can run the graph and store the data from the sink as below:<br />
<br />
<syntaxhighlight lang="python">self.tb.run ()<br />
result_data = snk.data ()<br />
</syntaxhighlight><br />
Lastly we can run our comparison function that will tell us whether the numbers match up to 6 decimal places. We are using the assertFloatTuplesAlmostEqual instead of the "regular assert functions"https://docs.python.org/2/library/unittest.html#assert-methods included in python's unittest because there may be situations where we cannot get a=b due to rounding in floating point numbers.<br />
<br />
<syntaxhighlight lang="python"><br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
All together the new test_001_t function should appear as below:<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
expected_result = (0, 2, -4, 11, -1)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
snk = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, snk)<br />
self.tb.run ()<br />
result_data = snk.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
<br />
We can then go to the python directory and run:<br />
<br />
<pre><br />
gr-tutorial/python$ python qa_multiply_py_ff.py<br />
.<br />
----------------------------------------------------------------------<br />
Ran 1 test in 0.004s<br />
<br />
OK<br />
</pre><br />
<br />
While we are here, we should take a break to change one of the numbers in the src_data to ensure that the block is actually checking the values and to simply see what an error looks like. Python allows for really quick testing of blocks without having to compile anything; simply change something and re-run the QA test.<br />
<br />
=== 3.2.5. XML Files ===<br />
<br />
At this point we should have written a Python block and a QA test for that block. The next thing to do is edit the XML file in the grc folder so that we can get another step closer to using it in GRC. GRC uses the XML files for all the options we see. We actually don't need to write any Python or C++ code to have a block display in GRC but of course if we would connect it, it wouldn't do anything but give errors. We should get out of the python folder and go to the grc folder where all the XML files reside. There is a tool in gr_modtool called makexml but it is only available for C++ blocks. Let us open the tutorial_multiply_py_ff.xml file copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>multiply_py_ff</name><br />
<key>tutorial_multiply_py_ff</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.multiply_py_ff($multiple)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>...</name><br />
<key>...</key><br />
<type>...</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<source><br />
<name>out</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</source><br />
</block></syntaxhighlight><br />
<br />
We can change the name that appears and the category it will appear in GRC. The category is where the block will be found in GRC. Examples of categories tag are '''Audio''' and '''Waveform Generators''' used in previous examples. Examples of names tag are the '''QT GUI Time Sink''' or the '''Audio Sink'''. Again, we can go through the file and find the modtool place holders. The first is copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
</syntaxhighlight><br />
This is referring to the parameter that we used in the very beginning when creating our block: the variable called "multiple". We can fill it in as below:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<param><br />
<name>Multiple</name><br />
<key>multiple</key><br />
<type>float</type><br />
</param><br />
</syntaxhighlight><br />
The next placeholder can be found in the sink and source tags:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
</syntaxhighlight><br />
We can see that it is asking for a type so we can simply erase everything in the tag and replace it with "float" for both the source and the sink blocks. That should do it for this block. The best way to get more experience writing xml files is to look at the source code of previously made blocks such as the existing multiple block. Let's go back to this and use the documentation tag!<br />
<br />
=== 3.2.6. Installing Python Blocks ===<br />
<br />
Now that we have edited the XML file; we are ready to install the block into the GRC. First, we need to get out of the /grc directory and create a build directory called "build". Inside the build directory, we can then run a series of commands:<br />
<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
<br />
We should then open up the GRC and take a look at the new block.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_blockname.png<br />
<br />
We can see the category and the block name. When we drag it into the GRC workspace, we can see the multiple variable we set in the param tag of the XML file.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_param.png<br />
<br />
Now that we can see our block, let us test it to make sure it works. Below is an example of one of the many ways to check whether it is actually multiplying.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_2.png<br />
<br />
== 3.3. My QPSK Demodulator for Satellites ==<br />
<br />
Now that we have a better understanding on how to add new blocks to GNU Radio for use in the GRC, we can do another example. This time we will be creating a QPSK demodulator block using the same process as before in the same module. Let's first setup the scenario. There is a "noise source" that outputs data in complex float format but let's pretend it comes from a satellite being aimed at our computer. Our secret agent insider tells us this particular satellite encodes digital data using QPSK modulation. We can decode this using a QPSK demodulator that outputs data into bytes. Our insider tells us the space manual doesn't specify whether it's gray code or not. We want to read the bytes using a time sink. What would our flowgraph look like?<br />
<br />
Incomplete Flowgraph...<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_3.png<br />
<br />
<br />
Now that we know the input type, output type, and parameters, we can ask the question we skipped with our multiply_py_ff block. What type of block do we want?<br />
<br />
=== 3.3.1. Choosing a Block Type ===<br />
<br />
The GNU Radio scheduler optimizes for throughput (as opposed to latency). To do this it needs to know the number of samples each block is inputting and outputting thus came the creation of decimation and interpolation blocks where we specify the multiple factor. This is directly related to the sampling rate discussion we had in tutorial 2 where these special types of blocks are able to change the sampling rate to something else in order to comply with a specific piece of hardware such as our soundcard or a specific modulation/demodulation.<br />
<br />
* Synchronous (1:1) - Number of items at input port equals the number of items at output port. An example is the multiply_py_ff block. <br />
* Decimation (N:1) - Number of input items is a fixed multiple of the number of output items. Examples include filters such as decimating high pass and low pass filters (e.g., for sampling rate conversion).<br />
* Interpolation (1:M) - Number of output items is a fixed multiple of the number of input items. An example is the Interpolating FIR Filter.<br />
* General/Basic (N:M) - Provides no relation between the number of input items and the number of output items. An example is the rational resampler block which can be either a sync, decimator, or interpolator.<br />
<br />
When we insert data into our block, do we need more samples or less samples? Or put another way, should our sample rate change?<br />
<br />
What Type of Block Should we Use?<br />
* Sync Block or Basic Block<br />
<br />
<br />
=== 3.3.2. Adding Another Block to our OOT Module ===<br />
<br />
Now we know everything we need to know to create the block in gr_modtool. As a refresher, what would our gr_modtool command be?<br />
<br />
gr_modtool command...<br />
<pre><br />
gr-tutorial$ gr_modtool add -t sync -l python<br />
<br />
name: qpsk_demod_py_cb (c for complex input and b for byte/char/int8 output)<br /><br />
args: gray_code<br /><br />
QA Code: y<br />
</pre><br />
<br />
<br />
Now we have our files setup so we can begin with editing the qpsk_demod_py.py file in the /python folder<br />
<br />
=== 3.3.3. The QPSK Demodulator ===<br />
<br />
The code here is a little more than we have done before so let's take it in steps. First what do we expect our block to do? We can start with a constellation plot which shows the four different quadrants and their mappings to bit 0 and bit 1<br />
<br />
With [http://en.wikipedia.org/wiki/Phase-shift_keying#Quadrature_phase-shift_keying_.28QPSK.29 Gray coding] (adjacent only flips by 1). Note that the mapping we use here is different from the mapping on wikipedia:<br />
<br />
<br />
(-1+1j) 10 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 11 | 01 (1-1j)<br />
<br />
Without Gray coding:<br />
<br />
(-1+1j) 11 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 10 | 01 (1-1j)<br />
<br />
We can see that we will need to write an if-else statement to take into account the gray_code variable. We will also need four other if-else statements inside the main if-else so that we can pick the mapping. Our pseudocode will look as follows:<br />
<br />
if gray_code<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "11" = 3<br />
else<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "10" = 2<br />
<br />
So we have everything we need to implement. Let's go ahead and fill in our gr_modtoool placeholders. We can begin with def init. There are three changes. How do we use the variable gray_code outside the function (similar to what we did with multiple in the last example)? What are the input and output types in [http://docs.scipy.org/doc/numpy/user/basics.types.html? numpy]<br />
<br />
Changes to def ''init''...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
</syntaxhighlight><br />
<br />
Once we have our constructor setup, we can go onto the work function. For simplicity and beauty, let us call the pseudocode we made above a function "get_minimum_distance" that takes samples as input arguments. In our multiply_py_ff example, we took all the samples and multiplied them with with out[:] = in0*self.multiple. The in0 is actually a vector so contains many samples within it. The multiply example required the same operation for each sample so it was okay to simply operate on the entire vector but now we need to have different operations per sample so what do we do?<br />
<br />
How can we operate on samples in a vector?<br />
* loops!<br />
<br />
<syntaxhighlight lang="python"><br />
for i in range(0, len(output_items[0])):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
</syntaxhighlight><br />
<br />
<br />
Now we can move onto the get_minimum_distances(self, sample) function. We already have pseudo code so the next step is translating to Python. Below is a snip of what the code can look like. Again there are multiple ways to do this<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
</syntaxhighlight><br />
Let us try to fill in the other cases for gray code and non-gray code. Below is what the entire file Python file can look like once complete:<br />
<br />
qpsk_demod_py_cb.py<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class qpsk_demod_py(gr.sync_block):<br />
"""<br />
docstring for block qpsk_demod_py<br />
"""<br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
<br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 3 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
else:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 3 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 2 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
<br />
for i in range(0, len(in0)):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
<br />
<br />
Now that we have code, we know what's next!<br />
<br />
=== 3.3.4. Multiple QA Tests ===<br />
<br />
We can test our qpsk_demod_py for when it is in gray_code mode and when it's not in gray_code mode. To do that we need to setup multiple tests in our single QA file. QA tests generally follow the same setup from before. We select some inputs as tests and we check them against what we expect the outputs to be. The only difference from the multiply qa test is that this qa test requires more cases. There are four quadrants we need to map and two modes so in total there are eight test cases. We can open up our qa_qpsk_demod_py_ff.py file to change things.<br />
<br />
We can copy the def test_001_t from the qa_multiply_py_ff code which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def test_001_t (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (-6, 8, -11, 4, 6)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
This time we are working with a complex input so our src = blocks.vector_source_f must change. If we use the search bar in the manual we can find the other options:<br />
<br />
PIC of SEARCH<br />
<br />
b - bytes/unsigned char/int8<br /><br />
c - complex<br /><br />
f - float<br /><br />
i - int<br /><br />
s - short<br />
<br />
What do we change our source and sink vectors to?<br /><br />
src = blocks.vector_source_c (src_data)<br /><br />
dst = blocks.vector_sink_b ()<br /><br />
<br />
<br />
Before we move onto actual test cases, let us decide which mode we are testing for the test_001_t. We can create a new variable and assign it False (translates to 0) to test non-Gray code<br />
<br />
<syntaxhighlight lang="python"><br />
gray_code = False<br />
</syntaxhighlight><br />
Once we know we want to test non gray_code mappings, we can refer to our chart above and start placing in the proper inputs and outputs into the src_data and the expected_results. For instance if we were testing only two cases for non gray_code, we would do:<br />
<br />
<syntaxhighlight lang="python" line="line">src_data = ((-1-1j), (-1+1j))<br />
expected_result = (2, 3)<br />
</syntaxhighlight><br />
Last thing to do is call upon our new block in the "qpsk =" line and pass it the gray_code parameter<br />
<br />
qpsk = ?<br />
* qpsk = qpsk_demod_py_cb (gray_code)<br />
<br />
Now that we are done with the non gray_code test, we can simply create another test "def test_002_t (self):" and copy the contents underneath making sure that for this test we set gray_code = True and change the expected_result so it matches gray_code mapping. The full test is copied below:<br />
<br />
Full QA QPSK Demod Code...<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import numpy<br />
from qpsk_demod_py_cb import qpsk_demod_py<br />
<br />
class qa_qpsk_demod (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
gray_code = False<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (2, 3, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
def test_002_t (self):<br />
gray_code = True<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (3, 2, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qpsk_demod_py_cb.xml")<br />
</syntaxhighlight><br />
<br />
<br />
We can then run the test in Python and all should say something similar to:<br />
<br />
Ran 2 tests in 0.005s<br />
<br />
OK<br />
<br />
So once we verify it works as expected, we can then edit our XML file so that it is usable inside GRC.<br />
<br />
=== 3.3.5. XML Mods, Installation, and Running ===<br />
<br />
This XML is very similar to the XML file for the multiply_py_ff block so all we need to do is set the gray_code parameter and pick the correct input (complex) and output (byte) types. A copy of the full XML file is below:<br />
<br />
XML File for QPSK Demod<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>qpsk_demod_py</name><br />
<key>tutorial_qpsk_demod_py</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.qpsk_demod_py($gray_code)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<type>int</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<!-- e.g. int, float, complex, byte, short, xxx_vector, ...--><br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</block><br />
<br />
</syntaxhighlight><br />
<br />
<br />
We can then install as we did for the multiply block however we need to rerun cmake in order to take into account the new block:<br />
<pre><br />
cd build<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
</pre><br />
Then we can open up our GRC file from the beginning and place our missing block we just made.<br />
<br />
What is the Expected Output?<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_4.png<br />
<br />
== 3.4. Conclusion ==<br />
<br />
And that is it for now with Python. Let us know your thoughts before going on to the [[Guided_Tutorial_GNU_Radio_in_C++|C++ tutorial]].<br />
<br />
=== 3.4.1. Questions We Should Now be Able to Answer ===<br />
<br />
1. How do we set input- and output signatures in Python blocks?<br /><br />
2. Consider this I/O signature: (FIXME). Which input types and output types does it take?<br />
<br />
=== 3.4.2. Links to Further Information ===<br />
<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/BlocksCodingGuide Blocks Coding Guide]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModules Out-of-Tree Modules]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/TutorialsWritePythonApplications Writing Python Applications]<br />
<br />
== 3.5. Candidates for Future Sections ==<br />
<br />
Possible topics we may want to add depending on feedback and questions on the mailing list<br /><br />
- How to add documentation to blocks<br /><br />
- Constructor arguments, History, Output Multiple<br />
<br />
<br />
-----<br />
<br />
&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GNU_Radio_in_Python&diff=6420Guided Tutorial GNU Radio in Python2019-12-15T08:39:21Z<p>Cmrincon: update to v3.8.0.0</p>
<hr />
<div>&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
<br />
= Tutorial: Working with GNU Radio in Python =<br />
<br />
== Objectives ==<br />
<br />
* Python Blocks<br />
* OOT modules make the actual apps / functionality (GR is the API!)<br />
* How to add OOTs<br />
* How to add Python blocks with gr_modtool and how to code them<br />
* QPSK mapping<br />
* How to add GRC bindings for block<br />
<br />
== Prerequisites ==<br />
<br />
* Working Installation of GNU Radio 3.8 or later<br />
* [[Guided_Tutorial_GRC|GRC Tutorial]] (Recommended)<br />
* Familiar with Python<br />
<br />
-----<br />
<br />
== 3.1. Intro to Using GNU Radio with Python ==<br />
<br />
This tutorial goes through three parts. The first is how to modify, create, or simply understand the Python generated files GRC produces for us. The second is how to create our own custom out-of-tree (OOT) modules from the ground up. Lastly we go through an actual project to get more practice and build intuition on how we can use GNU Radio in our own project. As with the last tutorial, all the content - pictures, source code, and grc files - is included in the [https://github.com/gnuradio/gr-tutorial gr-tutorial repository] which we should have a local copy if we followed the directions from the [[Guided_Tutorial_GRC|GRC Tutorial]]<br />
<br />
Again we should have a directory with the solutions and a directory with our work as below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
/home/user/gnuradio/tutorials/solutions<br />
/home/user/gnuradio/tutorials/work<br />
</syntaxhighlight><br />
<br />
As a rule, if we hover over the GRC flowgraph images, we will be able to see the corresponding filename. Same applies for other images. Full code files are collapsed with the filename in the collapsed handle.<br />
<br />
=== 3.1.1. GRC Generated Python Files ===<br />
<br />
Let us look at a dial-tone example on the GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png<br />
<br />
When we click the '''Generate''' button, the terminal tells us it produced a .py file so let's open that to examine its code which is reproduced below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: tutorial_three_1<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
from gnuradio.filter import firdes<br />
import sys<br />
import signal<br />
from PyQt5 import Qt<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
<br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("tutorial_three_1")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.samp_rate = samp_rate = 32000<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.audio_sink_0 = audio.sink(samp_rate, '', True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
<br />
<br />
<br />
def main(top_block_cls=tutorial_three_1, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
The first thing for us to realize is that the GRC can generate Python files that we can then modify to do things we wouldn't be able to do in GNU Radio Companion such as perform [[TutorialsSimulations|simulations]]. The libraries available in Python open up a whole new realm of possibilities! For now, we will explore the structure of the GRC Python files so we are comfortable creating more interesting applications.<br />
<br />
=== 3.1.2. Hello World Dissected ===<br />
<br />
While examining the code, we need to get familiar with documentation. GNU Radio uses Doxygen (the software) for the [http://gnuradio.org/doc/doxygen/ GNU Radio Manual]. The easiest way to go through the documentation is to go through the functions that we use so let us simplify our code by only including the bare bones needed to run the dial-tone example.<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env Python3<br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, "")<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass<br />
<br />
</syntaxhighlight><br />
<br />
Let us examine this line by line:<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
</syntaxhighlight><br />
Tells the shell that this file is a Python file and to use the Python interpreter to run this file. Should always be included at the top to run from the terminal.<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
</syntaxhighlight><br />
<br />
Tells Python the modules to include. We must always have '''gr''' to run GNU Radio applications. The audio sink is included in the audio module and the signal_source_f is included in the analog module which is why we include them. [http://legacy.python.org/dev/peps/pep-0008/ PEP8] tells us we should import every module on its own line.<br />
<br />
<syntaxhighlight lang="python"><br />
class my_top_block(gr.top_block):<br />
</syntaxhighlight><br />
Define a class called "my_top_block" which is derived from another class, '''gr.top_block'''. This class is basically a container for the flow graph. By deriving from gr.top_block, we get all the hooks and functions we need to add blocks and interconnect them.<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self):<br />
</syntaxhighlight><br />
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.<br />
<br />
<syntaxhighlight lang="python"><br />
gr.top_block.__init__(self)<br />
</syntaxhighlight><br />
The parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br />
<br />
<syntaxhighlight lang="python"><br />
sample_rate = 32000<br />
ampl = 0.1<br />
</syntaxhighlight><br />
Variable declarations for sampling rate and amplitude that we will later use.<br />
<br />
=== 3.1.3. A Look at Documentation ===<br />
<br />
<syntaxhighlight lang="python"><br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
</syntaxhighlight><br />
Here we are using functions from GNU Radio so let's have a look at the documentation for '''analog.sig_source_f''' which is available in [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html the GNU Radio manual]. We can find it easily by using the search function as below:<br />
<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_search.png<br />
<br />
We can then scroll down to '''Member Function Documentation''' to see how the function is used and the parameters it accepts as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_source.png<br />
<br />
We can see that our function '''analog.sig_source_f''' takes in 5 parameters but in our code we are only using 4. There is no error because the last input '''offset''' is set to "0" by default as shown in the documentation. The first input is the '''sampling_freq''' which we defined as '''sample_rate''' in our code. The second input is asking for a '''gr::analog::gr_waveform_t''' waveform so let's click that [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177 link] to find out more.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_waveform.png<br />
<br />
We can see that there are a couple of options to choose from. In this case we chose '''analog.GR_SIN_WAVE'''. The third input is '''wave_freq''' which we input "350" or "440". The fourth input is '''ampl''' which we defined as '''ampl'''.<br />
<br />
<syntaxhighlight lang="python"><br />
dst = audio.sink(sample_rate, "")<br />
</syntaxhighlight><br />
Because documentation is so important, let's look at another example. Again, we can look at the documentation for '''audio.sink''' which is available on the GNU Radio Manual through the search function:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink.png<br />
<br />
We can then as before scroll down to the '''Member Function Documentation''' to see the parameters it accepts:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink_detail.png<br />
<br />
This time we have 3 inputs with the last being optional. In our code, for the first input '''sampling_rate''' we used are '''sample_rate''' variable. In the second input, we have a choice for what device to use for audio output. If we leave it alone as "" then it'll choose the default on our machine.<br />
<br />
=== 3.1.4. Connecting the Block Together ===<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
</syntaxhighlight><br />
The general syntax for connecting blocks is self.connect(block1, block2, block3, ...) which would connect the output of block1 with the input of block2, the output of block2 with the input of block3 and so on. We can connect as many blocks as we wish with one connect() call. However this only work when there is a one-to-one correspondence. If we go back to our initial flowgraph, there are 2 inputs to the '''Audio Sink''' block. The way to connect them is by using the syntax above. The first line connects the only output of src0 (350 Hz waveform) to the first input of dst (Audio Sink). The second line connects the only output of src1 (440 Hz waveform) to the second input of dst (Audio Sink). The code so far is equivalent to the flowgraph we have created in the beginning; the rest of the lines simply start the flowgraph and provide a keyboard interrupt.<br />
<br />
<syntaxhighlight lang="python"><br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except KeyboardInterrupt:<br />
pass<br />
</syntaxhighlight><br />
<br />
Luckily we are past the early years of GNU Radio when there was no GRC to make the Python files for us. Nowadays we can simply click things together in GRC instead of having to write code in Python to build flowgraphs. Still, a good understanding of what is going on every time we run GRC is good to know as it gives us more control of what we want the program to do.<br />
<br />
=== 3.1.5. Modifying the GRC Generated Python File ===<br />
<br />
For instance, what if we wanted to change variables such as frequency and amplitude when a certain event occurs. How do we implement if statements, state machines, etc in GNU Radio? One way is to create our own blocks which we will delve into at length later. Another is to modify our GRC generated python file.<br />
<br />
Our friend heard we were into RF so being cheap he has asked us to power his house using RF. He wants us to give him high power during the day so he can watch TV and play video games while at night give him low power so he power his alarm clock to wake up for work in the morning. We first need to setup the clock which keeps track of the day and gives us 1 for day or 0 for night. Once we get the time we can send him power through a sine wave using our massive terawatt amplifier and massive dish in our backyard. We did the calculations and we want to pulse at 1kHz, 1 amplitude during the day and 100Hz, 0.3 amplitude at night. Here's what we came up with in GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else.png<br />
<br />
<span style="color:blue">- '''Frequency''' to "freq", '''Amplitude''' to "ampl"</span><br /><br />
<span style="color:red">- '''ID''' to "probe"</span><br /><br />
- Everything else is visible<br />
<br />
The top section keeps track of time and will switch from 0 to 1 while the bottom section sends the pulse. The problem we encounter however is that there is no if-statement block. Sure we can tie the probe to the frequency as we did in tutorial2 for the singing sine wave but that only allows changing by a factor. What if we wanted to change multiple things and not by a linear factor? Let's start by running the flowgraph to make sure we get the output as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else_output.png<br />
<br />
Now we can open up the GRC generated python file if_else.py which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.get_number()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We are only concerned about a couple of parts namely the part where the probe is being read:<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
We can see that the variable '''val''' is obtaining the value of the probe block. We can write our if-else statement here based on the value of '''val''' to change the amplitude and frequency of our sine wave. But how do we change the frequency and amplitude? We can use the part where the '''QT GUI Entry''' updates the flowgraph. For the variable freq:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
</syntaxhighlight><br />
<br />
and for the variable ampl:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
</syntaxhighlight><br />
We can see that the functions set_ampl and set_freq can be used for just that - setting the amplitude and the frequency. Thus we can go back and modify our probe function with the if-else statement to give power to our friend.<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_funct ion_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
<br />
Now there is one more thing we need to take care of. GRC has compiled the python file in the order of creation of the elements, which was okay as long as there were no crossreferences. With the introduced adaptation (calling set_ampl and set_freq inside the _variable_function_probe_0_probe()) we need to fix the order of declarations. As set_ampl and set_freq both modify parameters of analog_sig_source_x_0_0 but analog_sig_source_x_0_0 is not instantiated before line 150, we have to move the declaration of the _variable_function_probe_0_probe() and everything related below that.<br />
<br />
<syntaxhighlight lang="python"><br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
</syntaxhighlight><br />
<br />
Full code copied below:<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print (val)<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We can then simply run our flowgraph from outside of GRC using<br />
<br />
<pre><br />
$ python3 if_else_mod.py<br />
</pre><br />
We should be able to see the numbers 0 and 1 on the terminal and the sine wave changing amplitude and frequency as the numbers change.<br />
<br />
This tutorial is merely an introduction on using python in GNU Radio, for a more advanced tutorial see [[TutorialsWritePythonApplications]] .<br />
<br />
== 3.2. Where Do Blocks Come From? ==<br />
<br />
Now that we have covered some of the ways we can modify our GRC generated Python files, we can see how to make our own blocks to add functionality to suit our specific project needs. Let us start off simple to get a feel for the waters. We will be making a block that is able to multiply a signal by the number we specify. The first thing we need to realize is that GNU Radio comes with gr_modtool, a utility that makes creating OOT Modules easy. Let us open up a terminal and begin in a project folder for the tutorial three.<br />
<br />
=== 3.2.1. Using gr_modtool ===<br />
<br />
Before we begin, we need to figure out what the commands for gr_modtool are so let's ask for help.<br />
<br />
<pre>$ gr_modtool --help<br />
Usage: gr_modtool [OPTIONS] COMMAND [ARGS]...<br />
<br />
A tool for editing GNU Radio out-of-tree modules.<br />
<br />
Options:<br />
--help Show this message and exit.<br />
<br />
Commands:<br />
add Adds a block to the out-of-tree module.<br />
disable Disable selected block in module.<br />
info Return information about a given module<br />
makeyaml Generate YAML files for GRC block bindings.<br />
newmod Create new empty module, use add to add blocks.<br />
rename Rename a block inside a module.<br />
rm Remove a block from a module.<br />
update Update the grc bindings for a block<br />
<br />
Manipulate with GNU Radio modules source code tree. Call it without<br />
options to run specified command interactively<br />
<br />
</pre><br />
We immediately see there are many commands available. In this tutorial we will only cover '''newmod''' and '''add'''; however, the thorough explanation should enable smooth usage of the other gr_modtool commands without guidance.<br />
<br />
First, we notice that in addition to getting help seeing the commands we can use, we can also request more information on individual commands. Let us start with newmod as that is the command to create a new out-of-tree module.<br />
<br />
<pre><br />
$ gr_modtool newmod --help<br />
Usage: gr_modtool newmod [OPTIONS] MODULE-NAME<br />
<br />
Create a new out-of-tree module<br />
<br />
The argument MODULE-NAME is the name of the module to be added.<br />
<br />
Options:<br />
--srcdir TEXT Source directory for the module template.<br />
-d, --directory TEXT Base directory of the module. Defaults to the cwd.<br />
--skip-lib Don't do anything in the lib/ subdirectory.<br />
--skip-swig Don't do anything in the swig/ subdirectory.<br />
--skip-python Don't do anything in the python/ subdirectory.<br />
--skip-grc Don't do anything in the grc/ subdirectory.<br />
--scm-mode [yes|no|auto] Use source control management [ yes | no | auto ]).<br />
-y, --yes Answer all questions with 'yes'. This can<br />
overwrite and delete your files, so be careful.<br />
--help Show this message and exit.<br />
<br />
</pre><br />
Now that we have read over the list of commands for newmod, we can deduce that the one we want to pick is -n which is the default so we can simply type the MODULE_NAME after newmod. It is actually advised to avoid using "-n" as for other commands it can override the auto-detected name. For now, let's ignore the other options.<br />
<br />
=== 3.2.2. Setting up a new block ===<br />
<br />
<pre><br />
$ gr_modtool newmod tutorial<br />
Creating out-of-tree module in ./gr-tutorial... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.<br />
</pre><br />
We should now see a new folder, gr-tutorial, in our current directory. Let's examine the folder to figure out what gr_modtool has done for us.<br />
<br />
<pre><br />
gr-tutorial$ ls<br />
apps cmake CMakeLists.txt docs examples grc include lib Python swig<br />
</pre><br />
Since we are dealing with Python in this tutorial we only need to concern ourselves with the Python folder and the grc folder. Before we can dive into code, we need to create a block from a template. There are actually four different types of Python blocks, however it's a little too soon to discuss that. We will dive into the synchronous 1:1 input to output block to make explaining things easier (this is a block that outputs as many items as it gets as input, but don't worry about this right now).<br />
<br />
Now we know the language we want to write our block in (Python) and the type of block (synchronous block) we can now add a block to our module. Again, we should run the gr_modtool help command until we are familiar with the different commands. We see that the '''add''' command is what we are looking for. Now we need to run help on the add command in order to see what we need to enter given the information we have so far.<br />
<br />
<pre><br />
gr-tutorial$ gr_modtool help add<br />
... (General Options from Last Help)<br />
Add module options:<br />
-t BLOCK_TYPE, --block-type=BLOCK_TYPE<br />
One of sink, source, sync, decimator, interpolator,<br />
general, tagged_stream, hier, noblock.<br />
--license-file=LICENSE_FILE<br />
File containing the license header for every source<br />
code file.<br />
--argument-list=ARGUMENT_LIST<br />
The argument list for the constructor and make<br />
functions.<br />
--add-Python-qa If given, Python QA code is automatically added if<br />
possible.<br />
--add-cpp-qa If given, C++ QA code is automatically added if<br />
possible.<br />
--skip-cmakefiles If given, only source files are written, but<br />
CMakeLists.txt files are left unchanged.<br />
-l LANG, --lang=LANG<br />
Language (cpp or Python)<br />
</pre><br />
We can see the '''-l LANG''' and the '''-t BLOCK_TYPE''' are relevant for our example. Thus when creating our new block, we know the command. When prompted for a name simply enter "multiply_py_ff", when prompted for an argument list enter "multiple", and when prompted for Python QA (Quality Assurance) code type "y", or just hit enter (the capital letter is the default value).<br />
<br />
<pre>gr-tutorial$ gr_modtool add -t sync -l python<br />
GNU Radio module name identified: tutorial<br />
Language: Python<br />
Enter name of block/code (without module name prefix): multiply_py_ff<br />
Block/code identifier: multiply_py_ff<br />
Enter valid argument list, including default arguments: multiple<br />
Add Python QA code? [Y/n] y<br />
Adding file 'Python/multiply_py_ff.py'...<br />
Adding file 'Python/qa_multiply_py_ff.py'...<br />
Editing Python/CMakeLists.txt...<br />
Adding file 'grc/tutorial_multiply_py_ff.xml'...<br />
Editing grc/CMakeLists.txt...<br />
</pre><br />
We notice 5 changes: Two changes to CMakeLists.txt files, one new file <code>qa_multiply_py_ff.py</code> which is used to test our code, one new file <code>multiply_py_ff.py</code> which is the functional part, and one new file <code>tutorial_multiply_py_ff.xml</code>, which is used to link the block to the GRC. Again all this happens in the Python and grc subfolders.<br />
<br />
==== 3.2.2.1. What's with the <code>_ff</code>? ====<br />
<br />
For blocks with strict types, we use suffixes to declare the input and output types. This block operates on floats, so we give it the suffix <code>_ff</code>: Float in, float out. Other suffixes are <code>_cc</code> (complex in, complex out), or simply <code>_f</code> (a sink or source with no in- or outputs that uses floats). For a more detailed description, see the [[FAQ]] or the [[BlocksCodingGuide]].<br />
<br />
=== 3.2.3. Modifying the Python Block File ===<br />
<br />
Let's begin with the multiply_py_ff.py file found in the Python folder. Opening it without any changes gives the following:<br />
<br />
<syntaxhighlight lang="python"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class multiply_py_ff(gr.sync_block):<br />
"""<br />
docstring for block multiply_py_ff<br />
"""<br />
def __init__(self, multiple):<br />
gr.sync_block.__init__(self,<br />
name="multiply_py_ff",<br />
in_sig=[<+numpy.float+>],<br />
out_sig=[<+numpy.float+>])<br />
self.multiple = multiple<br />
<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
Let's take this one line by line as our first Python examples. We are already familiar with the imports so we will skip those lines. We are familiar with the constructor (init) of Python so can immediately see that if we want to use our variable "multiple", we need to add another line. Let us not forget to preserve those spaces as some code editors like to add tabs to new lines. How do we use the variable multiple?<br />
<br />
How to use variable multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, multiple):<br />
self.multiple = multiple<br />
gr.sync_block.__init__(self,<br />
</syntaxhighlight><br />
<br />
<br />
We notice that there are "&lt;''...''&gt;" scattered in many places. These placeholders are from gr_modtool and tell us where we need to alter things<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[<+numpy.float+>]<br />
out_sig=[<+numpy.float+>]<br />
</syntaxhighlight><br />
The '''gr.sync_block.''init''''' takes in 4 inputs: self, name, and the size/type of the input and output vectors. First, we want to make the item size a single precision float or numpy.float32 by removing the "&lt;''" and the "''&gt;". If we wanted vectors, we could define those as in_sig=[(numpy.float32,4),numpy.float32]. This means there are two input ports, one for vectors of 4 floats and the other for scalars. It is worth noting that if in_sig contains nothing then it becomes a source block, and if out_sig contains nothing it becomes a sink block (provided we change return <code>len(output_items[0])</code> to return <code>len(input_items[0])</code> since output_items is empty). Our changes to the first placeholders should appear as follows:<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[numpy.float32]<br />
out_sig=[numpy.float32]<br />
</syntaxhighlight><br />
The other piece of code that has the placeholders is in the work function but let us first get a better understanding of the work function:<br />
<br />
<syntaxhighlight lang="python"><br />
def work(self, input_items, output_items)<br />
</syntaxhighlight><br />
The work function is where the actual processing happens, where we want our code to be. Because this is a sync block, the number of input items always equals the number of output items because synchronous block ensures a fixed output to input rate. There are also decimation and interpolation blocks where the number of output items are a user specified multiple of the number of input items. We will further discuss when to use what block type in the third section of this tutorial. For now we look at the placeholder:<br />
<br />
<syntaxhighlight lang="python"><br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
The "in0" and "out" simply store the input and output in a variable to make the block easier to write. The signal processing can be anything including if statements, loops, function calls but for this example we only need to modify the out[:] = in0 line so that our input signal is multiplied by our variable multiple. What do we need to add to make the in0 multiply by our multiple?<br />
<br />
How to Multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
out[:] = in0*self.multiple<br />
</syntaxhighlight><br />
<br />
That's it! Our block should now be able to multiply but to be sure we have these things called Quality Assurance tests!<br />
<br />
=== 3.2.4. QA Tests ===<br />
<br />
Now we need to test it to make sure it will run correctly when we install it unto GNU Radio. This is a very important step and we should never forget to include tests with our code! Let us open up qa_multiply_py_ff.py which is copied below:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
from multiply_py_ff import multiply_py_ff<br />
<br />
class qa_multiply_py_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
# set up fg<br />
self.tb.run ()<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_multiply_py_ff, "qa_multiply_py_ff.xml")<br />
</syntaxhighlight><br />
gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. The only part we need to worry about is the def test_001_t function. We know we need input data so let us create data. We want it to be in the form of a vector so that we can test multiple values at once. Let us create a vector of floats<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
</syntaxhighlight><br />
We also need output data so we can compare the input of the block to ensure that it is doing what we expect it to do. Let us multiply by 2 for simplicity.<br />
<br />
<syntaxhighlight lang="python">expected_result = (0, 2, -4, 11, -1)<br />
</syntaxhighlight><br />
Now we can create a flowgraph as we have when we first introduced using Python in GNU Radio. We can use the blocks library specifically the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__source__f.html vector_source_f] function and the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__sink__f.html vector_sink_f] function which are linked to the doxygen manual which we should be able to read and understand. Let us assign three variables "src", "mult", and "snk" to the blocks. The first is shown below:<br />
<br />
<syntaxhighlight lang="python">src = blocks.vector_source_f(src_data)</syntaxhighlight><br />
The rest are hidden below as an exercise:<br />
<br />
What do we assign snk and mult?<br />
<br />
<syntaxhighlight lang="python">mult = multiply_py_ff(2)<br />
snk = blocks.vector_sink_f()<br />
</syntaxhighlight><br />
<br />
<br />
Now we need to connect everything as src <s>&gt; mult</s>&gt; snk. Instead of using self.connect as we did in our other blocks we need to use self.tb.connect because of the setUp function. Below is how we would connect the src block to the mult block.<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (src, mult)<br />
</syntaxhighlight><br />
<br />
How would we connect the other blocks together?<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (mult, snk)<br />
</syntaxhighlight><br />
<br />
<br />
Then we can run the graph and store the data from the sink as below:<br />
<br />
<syntaxhighlight lang="python">self.tb.run ()<br />
result_data = snk.data ()<br />
</syntaxhighlight><br />
Lastly we can run our comparison function that will tell us whether the numbers match up to 6 decimal places. We are using the assertFloatTuplesAlmostEqual instead of the "regular assert functions"https://docs.python.org/2/library/unittest.html#assert-methods included in python's unittest because there may be situations where we cannot get a=b due to rounding in floating point numbers.<br />
<br />
<syntaxhighlight lang="python"><br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
All together the new test_001_t function should appear as below:<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
expected_result = (0, 2, -4, 11, -1)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
snk = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, snk)<br />
self.tb.run ()<br />
result_data = snk.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
<br />
We can then go to the python directory and run:<br />
<br />
<pre><br />
gr-tutorial/python$ python qa_multiply_py_ff.py<br />
.<br />
----------------------------------------------------------------------<br />
Ran 1 test in 0.004s<br />
<br />
OK<br />
</pre><br />
<br />
While we are here, we should take a break to change one of the numbers in the src_data to ensure that the block is actually checking the values and to simply see what an error looks like. Python allows for really quick testing of blocks without having to compile anything; simply change something and re-run the QA test.<br />
<br />
=== 3.2.5. XML Files ===<br />
<br />
At this point we should have written a Python block and a QA test for that block. The next thing to do is edit the XML file in the grc folder so that we can get another step closer to using it in GRC. GRC uses the XML files for all the options we see. We actually don't need to write any Python or C++ code to have a block display in GRC but of course if we would connect it, it wouldn't do anything but give errors. We should get out of the python folder and go to the grc folder where all the XML files reside. There is a tool in gr_modtool called makexml but it is only available for C++ blocks. Let us open the tutorial_multiply_py_ff.xml file copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>multiply_py_ff</name><br />
<key>tutorial_multiply_py_ff</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.multiply_py_ff($multiple)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>...</name><br />
<key>...</key><br />
<type>...</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<source><br />
<name>out</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</source><br />
</block></syntaxhighlight><br />
<br />
We can change the name that appears and the category it will appear in GRC. The category is where the block will be found in GRC. Examples of categories tag are '''Audio''' and '''Waveform Generators''' used in previous examples. Examples of names tag are the '''QT GUI Time Sink''' or the '''Audio Sink'''. Again, we can go through the file and find the modtool place holders. The first is copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
</syntaxhighlight><br />
This is referring to the parameter that we used in the very beginning when creating our block: the variable called "multiple". We can fill it in as below:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<param><br />
<name>Multiple</name><br />
<key>multiple</key><br />
<type>float</type><br />
</param><br />
</syntaxhighlight><br />
The next placeholder can be found in the sink and source tags:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
</syntaxhighlight><br />
We can see that it is asking for a type so we can simply erase everything in the tag and replace it with "float" for both the source and the sink blocks. That should do it for this block. The best way to get more experience writing xml files is to look at the source code of previously made blocks such as the existing multiple block. Let's go back to this and use the documentation tag!<br />
<br />
=== 3.2.6. Installing Python Blocks ===<br />
<br />
Now that we have edited the XML file; we are ready to install the block into the GRC. First, we need to get out of the /grc directory and create a build directory called "build". Inside the build directory, we can then run a series of commands:<br />
<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
<br />
We should then open up the GRC and take a look at the new block.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_blockname.png<br />
<br />
We can see the category and the block name. When we drag it into the GRC workspace, we can see the multiple variable we set in the param tag of the XML file.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_param.png<br />
<br />
Now that we can see our block, let us test it to make sure it works. Below is an example of one of the many ways to check whether it is actually multiplying.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_2.png<br />
<br />
== 3.3. My QPSK Demodulator for Satellites ==<br />
<br />
Now that we have a better understanding on how to add new blocks to GNU Radio for use in the GRC, we can do another example. This time we will be creating a QPSK demodulator block using the same process as before in the same module. Let's first setup the scenario. There is a "noise source" that outputs data in complex float format but let's pretend it comes from a satellite being aimed at our computer. Our secret agent insider tells us this particular satellite encodes digital data using QPSK modulation. We can decode this using a QPSK demodulator that outputs data into bytes. Our insider tells us the space manual doesn't specify whether it's gray code or not. We want to read the bytes using a time sink. What would our flowgraph look like?<br />
<br />
Incomplete Flowgraph...<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_3.png<br />
<br />
<br />
Now that we know the input type, output type, and parameters, we can ask the question we skipped with our multiply_py_ff block. What type of block do we want?<br />
<br />
=== 3.3.1. Choosing a Block Type ===<br />
<br />
The GNU Radio scheduler optimizes for throughput (as opposed to latency). To do this it needs to know the number of samples each block is inputting and outputting thus came the creation of decimation and interpolation blocks where we specify the multiple factor. This is directly related to the sampling rate discussion we had in tutorial 2 where these special types of blocks are able to change the sampling rate to something else in order to comply with a specific piece of hardware such as our soundcard or a specific modulation/demodulation.<br />
<br />
* Synchronous (1:1) - Number of items at input port equals the number of items at output port. An example is the multiply_py_ff block. <br />
* Decimation (N:1) - Number of input items is a fixed multiple of the number of output items. Examples include filters such as decimating high pass and low pass filters (e.g., for sampling rate conversion).<br />
* Interpolation (1:M) - Number of output items is a fixed multiple of the number of input items. An example is the Interpolating FIR Filter.<br />
* General/Basic (N:M) - Provides no relation between the number of input items and the number of output items. An example is the rational resampler block which can be either a sync, decimator, or interpolator.<br />
<br />
When we insert data into our block, do we need more samples or less samples? Or put another way, should our sample rate change?<br />
<br />
What Type of Block Should we Use?<br />
* Sync Block or Basic Block<br />
<br />
<br />
=== 3.3.2. Adding Another Block to our OOT Module ===<br />
<br />
Now we know everything we need to know to create the block in gr_modtool. As a refresher, what would our gr_modtool command be?<br />
<br />
gr_modtool command...<br />
<pre><br />
gr-tutorial$ gr_modtool add -t sync -l python<br />
<br />
name: qpsk_demod_py_cb (c for complex input and b for byte/char/int8 output)<br /><br />
args: gray_code<br /><br />
QA Code: y<br />
</pre><br />
<br />
<br />
Now we have our files setup so we can begin with editing the qpsk_demod_py.py file in the /python folder<br />
<br />
=== 3.3.3. The QPSK Demodulator ===<br />
<br />
The code here is a little more than we have done before so let's take it in steps. First what do we expect our block to do? We can start with a constellation plot which shows the four different quadrants and their mappings to bit 0 and bit 1<br />
<br />
With [http://en.wikipedia.org/wiki/Phase-shift_keying#Quadrature_phase-shift_keying_.28QPSK.29 Gray coding] (adjacent only flips by 1). Note that the mapping we use here is different from the mapping on wikipedia:<br />
<br />
<br />
(-1+1j) 10 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 11 | 01 (1-1j)<br />
<br />
Without Gray coding:<br />
<br />
(-1+1j) 11 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 10 | 01 (1-1j)<br />
<br />
We can see that we will need to write an if-else statement to take into account the gray_code variable. We will also need four other if-else statements inside the main if-else so that we can pick the mapping. Our pseudocode will look as follows:<br />
<br />
if gray_code<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "11" = 3<br />
else<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "10" = 2<br />
<br />
So we have everything we need to implement. Let's go ahead and fill in our gr_modtoool placeholders. We can begin with def init. There are three changes. How do we use the variable gray_code outside the function (similar to what we did with multiple in the last example)? What are the input and output types in [http://docs.scipy.org/doc/numpy/user/basics.types.html? numpy]<br />
<br />
Changes to def ''init''...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
</syntaxhighlight><br />
<br />
Once we have our constructor setup, we can go onto the work function. For simplicity and beauty, let us call the pseudocode we made above a function "get_minimum_distance" that takes samples as input arguments. In our multiply_py_ff example, we took all the samples and multiplied them with with out[:] = in0*self.multiple. The in0 is actually a vector so contains many samples within it. The multiply example required the same operation for each sample so it was okay to simply operate on the entire vector but now we need to have different operations per sample so what do we do?<br />
<br />
How can we operate on samples in a vector?<br />
* loops!<br />
<br />
<syntaxhighlight lang="python"><br />
for i in range(0, len(output_items[0])):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
</syntaxhighlight><br />
<br />
<br />
Now we can move onto the get_minimum_distances(self, sample) function. We already have pseudo code so the next step is translating to Python. Below is a snip of what the code can look like. Again there are multiple ways to do this<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
</syntaxhighlight><br />
Let us try to fill in the other cases for gray code and non-gray code. Below is what the entire file Python file can look like once complete:<br />
<br />
qpsk_demod_py_cb.py<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class qpsk_demod_py(gr.sync_block):<br />
"""<br />
docstring for block qpsk_demod_py<br />
"""<br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
<br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 3 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
else:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 3 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 2 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
<br />
for i in range(0, len(in0)):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
<br />
<br />
Now that we have code, we know what's next!<br />
<br />
=== 3.3.4. Multiple QA Tests ===<br />
<br />
We can test our qpsk_demod_py for when it is in gray_code mode and when it's not in gray_code mode. To do that we need to setup multiple tests in our single QA file. QA tests generally follow the same setup from before. We select some inputs as tests and we check them against what we expect the outputs to be. The only difference from the multiply qa test is that this qa test requires more cases. There are four quadrants we need to map and two modes so in total there are eight test cases. We can open up our qa_qpsk_demod_py_ff.py file to change things.<br />
<br />
We can copy the def test_001_t from the qa_multiply_py_ff code which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def test_001_t (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (-6, 8, -11, 4, 6)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
This time we are working with a complex input so our src = blocks.vector_source_f must change. If we use the search bar in the manual we can find the other options:<br />
<br />
PIC of SEARCH<br />
<br />
b - bytes/unsigned char/int8<br /><br />
c - complex<br /><br />
f - float<br /><br />
i - int<br /><br />
s - short<br />
<br />
What do we change our source and sink vectors to?<br /><br />
src = blocks.vector_source_c (src_data)<br /><br />
dst = blocks.vector_sink_b ()<br /><br />
<br />
<br />
Before we move onto actual test cases, let us decide which mode we are testing for the test_001_t. We can create a new variable and assign it False (translates to 0) to test non-Gray code<br />
<br />
<syntaxhighlight lang="python"><br />
gray_code = False<br />
</syntaxhighlight><br />
Once we know we want to test non gray_code mappings, we can refer to our chart above and start placing in the proper inputs and outputs into the src_data and the expected_results. For instance if we were testing only two cases for non gray_code, we would do:<br />
<br />
<syntaxhighlight lang="python" line="line">src_data = ((-1-1j), (-1+1j))<br />
expected_result = (2, 3)<br />
</syntaxhighlight><br />
Last thing to do is call upon our new block in the "qpsk =" line and pass it the gray_code parameter<br />
<br />
qpsk = ?<br />
* qpsk = qpsk_demod_py_cb (gray_code)<br />
<br />
Now that we are done with the non gray_code test, we can simply create another test "def test_002_t (self):" and copy the contents underneath making sure that for this test we set gray_code = True and change the expected_result so it matches gray_code mapping. The full test is copied below:<br />
<br />
Full QA QPSK Demod Code...<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import numpy<br />
from qpsk_demod_py_cb import qpsk_demod_py<br />
<br />
class qa_qpsk_demod (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
gray_code = False<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (2, 3, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
def test_002_t (self):<br />
gray_code = True<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (3, 2, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qpsk_demod_py_cb.xml")<br />
</syntaxhighlight><br />
<br />
<br />
We can then run the test in Python and all should say something similar to:<br />
<br />
Ran 2 tests in 0.005s<br />
<br />
OK<br />
<br />
So once we verify it works as expected, we can then edit our XML file so that it is usable inside GRC.<br />
<br />
=== 3.3.5. XML Mods, Installation, and Running ===<br />
<br />
This XML is very similar to the XML file for the multiply_py_ff block so all we need to do is set the gray_code parameter and pick the correct input (complex) and output (byte) types. A copy of the full XML file is below:<br />
<br />
XML File for QPSK Demod<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>qpsk_demod_py</name><br />
<key>tutorial_qpsk_demod_py</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.qpsk_demod_py($gray_code)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<type>int</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<!-- e.g. int, float, complex, byte, short, xxx_vector, ...--><br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</block><br />
<br />
</syntaxhighlight><br />
<br />
<br />
We can then install as we did for the multiply block however we need to rerun cmake in order to take into account the new block:<br />
<pre><br />
cd build<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
</pre><br />
Then we can open up our GRC file from the beginning and place our missing block we just made.<br />
<br />
What is the Expected Output?<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_4.png<br />
<br />
== 3.4. Conclusion ==<br />
<br />
And that is it for now with Python. Let us know your thoughts before going on to the [[Guided_Tutorial_GNU_Radio_in_C++|C++ tutorial]].<br />
<br />
=== 3.4.1. Questions We Should Now be Able to Answer ===<br />
<br />
1. How do we set input- and output signatures in Python blocks?<br /><br />
2. Consider this I/O signature: (FIXME). Which input types and output types does it take?<br />
<br />
=== 3.4.2. Links to Further Information ===<br />
<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/BlocksCodingGuide Blocks Coding Guide]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModules Out-of-Tree Modules]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/TutorialsWritePythonApplications Writing Python Applications]<br />
<br />
== 3.5. Candidates for Future Sections ==<br />
<br />
Possible topics we may want to add depending on feedback and questions on the mailing list<br /><br />
- How to add documentation to blocks<br /><br />
- Constructor arguments, History, Output Multiple<br />
<br />
<br />
-----<br />
<br />
&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GNU_Radio_in_Python&diff=6419Guided Tutorial GNU Radio in Python2019-12-14T11:11:43Z<p>Cmrincon: update to v3.8.0.0</p>
<hr />
<div>&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
<br />
= Tutorial: Working with GNU Radio in Python =<br />
<br />
== Objectives ==<br />
<br />
* Python Blocks<br />
* OOT modules make the actual apps / functionality (GR is the API!)<br />
* How to add OOTs<br />
* How to add Python blocks with gr_modtool and how to code them<br />
* QPSK mapping<br />
* How to add GRC bindings for block<br />
<br />
== Prerequisites ==<br />
<br />
* Working Installation of GNU Radio 3.8 or later<br />
* [[Guided_Tutorial_GRC|GRC Tutorial]] (Recommended)<br />
* Familiar with Python<br />
<br />
-----<br />
<br />
== 3.1. Intro to Using GNU Radio with Python ==<br />
<br />
This tutorial goes through three parts. The first is how to modify, create, or simply understand the Python generated files GRC produces for us. The second is how to create our own custom out-of-tree (OOT) modules from the ground up. Lastly we go through an actual project to get more practice and build intuition on how we can use GNU Radio in our own project. As with the last tutorial, all the content - pictures, source code, and grc files - is included in the [https://github.com/gnuradio/gr-tutorial gr-tutorial repository] which we should have a local copy if we followed the directions from the [[Guided_Tutorial_GRC|GRC Tutorial]]<br />
<br />
Again we should have a directory with the solutions and a directory with our work as below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
/home/user/gnuradio/tutorials/solutions<br />
/home/user/gnuradio/tutorials/work<br />
</syntaxhighlight><br />
<br />
As a rule, if we hover over the GRC flowgraph images, we will be able to see the corresponding filename. Same applies for other images. Full code files are collapsed with the filename in the collapsed handle.<br />
<br />
=== 3.1.1. GRC Generated Python Files ===<br />
<br />
Let us look at a dial-tone example on the GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png<br />
<br />
When we click the '''Generate''' button, the terminal tells us it produced a .py file so let's open that to examine its code which is reproduced below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: tutorial_three_1<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
from gnuradio.filter import firdes<br />
import sys<br />
import signal<br />
from PyQt5 import Qt<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
<br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("tutorial_three_1")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.samp_rate = samp_rate = 32000<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.audio_sink_0 = audio.sink(samp_rate, '', True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
<br />
<br />
<br />
def main(top_block_cls=tutorial_three_1, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
The first thing for us to realize is that the GRC can generate Python files that we can then modify to do things we wouldn't be able to do in GNU Radio Companion such as perform [[TutorialsSimulations|simulations]]. The libraries available in Python open up a whole new realm of possibilities! For now, we will explore the structure of the GRC Python files so we are comfortable creating more interesting applications.<br />
<br />
=== 3.1.2. Hello World Dissected ===<br />
<br />
While examining the code, we need to get familiar with documentation. GNU Radio uses Doxygen (the software) for the [http://gnuradio.org/doc/doxygen/ GNU Radio Manual]. The easiest way to go through the documentation is to go through the functions that we use so let us simplify our code by only including the bare bones needed to run the dial-tone example.<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env Python3<br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, "")<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass<br />
<br />
</syntaxhighlight><br />
<br />
Let us examine this line by line:<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
</syntaxhighlight><br />
Tells the shell that this file is a Python file and to use the Python interpreter to run this file. Should always be included at the top to run from the terminal.<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
</syntaxhighlight><br />
<br />
Tells Python the modules to include. We must always have '''gr''' to run GNU Radio applications. The audio sink is included in the audio module and the signal_source_f is included in the analog module which is why we include them. [http://legacy.python.org/dev/peps/pep-0008/ PEP8] tells us we should import every module on its own line.<br />
<br />
<syntaxhighlight lang="python"><br />
class my_top_block(gr.top_block):<br />
</syntaxhighlight><br />
Define a class called "my_top_block" which is derived from another class, '''gr.top_block'''. This class is basically a container for the flow graph. By deriving from gr.top_block, we get all the hooks and functions we need to add blocks and interconnect them.<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self):<br />
</syntaxhighlight><br />
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.<br />
<br />
<syntaxhighlight lang="python"><br />
gr.top_block.__init__(self)<br />
</syntaxhighlight><br />
The parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br />
<br />
<syntaxhighlight lang="python"><br />
sample_rate = 32000<br />
ampl = 0.1<br />
</syntaxhighlight><br />
Variable declarations for sampling rate and amplitude that we will later use.<br />
<br />
=== 3.1.3. A Look at Documentation ===<br />
<br />
<syntaxhighlight lang="python"><br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
</syntaxhighlight><br />
Here we are using functions from GNU Radio so let's have a look at the documentation for '''analog.sig_source_f''' which is available in [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html the GNU Radio manual]. We can find it easily by using the search function as below:<br />
<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_search.png<br />
<br />
We can then scroll down to '''Member Function Documentation''' to see how the function is used and the parameters it accepts as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_source.png<br />
<br />
We can see that our function '''analog.sig_source_f''' takes in 5 parameters but in our code we are only using 4. There is no error because the last input '''offset''' is set to "0" by default as shown in the documentation. The first input is the '''sampling_freq''' which we defined as '''sample_rate''' in our code. The second input is asking for a '''gr::analog::gr_waveform_t''' waveform so let's click that [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177 link] to find out more.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_waveform.png<br />
<br />
We can see that there are a couple of options to choose from. In this case we chose '''analog.GR_SIN_WAVE'''. The third input is '''wave_freq''' which we input "350" or "440". The fourth input is '''ampl''' which we defined as '''ampl'''.<br />
<br />
<syntaxhighlight lang="python"><br />
dst = audio.sink(sample_rate, "")<br />
</syntaxhighlight><br />
Because documentation is so important, let's look at another example. Again, we can look at the documentation for '''audio.sink''' which is available on the GNU Radio Manual through the search function:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink.png<br />
<br />
We can then as before scroll down to the '''Member Function Documentation''' to see the parameters it accepts:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink_detail.png<br />
<br />
This time we have 3 inputs with the last being optional. In our code, for the first input '''sampling_rate''' we used are '''sample_rate''' variable. In the second input, we have a choice for what device to use for audio output. If we leave it alone as "" then it'll choose the default on our machine.<br />
<br />
=== 3.1.4. Connecting the Block Together ===<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
</syntaxhighlight><br />
The general syntax for connecting blocks is self.connect(block1, block2, block3, ...) which would connect the output of block1 with the input of block2, the output of block2 with the input of block3 and so on. We can connect as many blocks as we wish with one connect() call. However this only work when there is a one-to-one correspondence. If we go back to our initial flowgraph, there are 2 inputs to the '''Audio Sink''' block. The way to connect them is by using the syntax above. The first line connects the only output of src0 (350 Hz waveform) to the first input of dst (Audio Sink). The second line connects the only output of src1 (440 Hz waveform) to the second input of dst (Audio Sink). The code so far is equivalent to the flowgraph we have created in the beginning; the rest of the lines simply start the flowgraph and provide a keyboard interrupt.<br />
<br />
<syntaxhighlight lang="python"><br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except KeyboardInterrupt:<br />
pass<br />
</syntaxhighlight><br />
<br />
Luckily we are past the early years of GNU Radio when there was no GRC to make the Python files for us. Nowadays we can simply click things together in GRC instead of having to write code in Python to build flowgraphs. Still, a good understanding of what is going on every time we run GRC is good to know as it gives us more control of what we want the program to do.<br />
<br />
=== 3.1.5. Modifying the GRC Generated Python File ===<br />
<br />
For instance, what if we wanted to change variables such as frequency and amplitude when a certain event occurs. How do we implement if statements, state machines, etc in GNU Radio? One way is to create our own blocks which we will delve into at length later. Another is to modify our GRC generated python file.<br />
<br />
Our friend heard we were into RF so being cheap he has asked us to power his house using RF. He wants us to give him high power during the day so he can watch TV and play video games while at night give him low power so he power his alarm clock to wake up for work in the morning. We first need to setup the clock which keeps track of the day and gives us 1 for day or 0 for night. Once we get the time we can send him power through a sine wave using our massive terawatt amplifier and massive dish in our backyard. We did the calculations and we want to pulse at 1kHz, 1 amplitude during the day and 100Hz, 0.3 amplitude at night. Here's what we came up with in GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else.png<br />
<br />
<span style="color:blue">- '''Frequency''' to "freq", '''Amplitude''' to "ampl"</span><br /><br />
<span style="color:red">- '''ID''' to "probe"</span><br /><br />
- Everything else is visible<br />
<br />
The top section keeps track of time and will switch from 0 to 1 while the bottom section sends the pulse. The problem we encounter however is that there is no if-statement block. Sure we can tie the probe to the frequency as we did in tutorial2 for the singing sine wave but that only allows changing by a factor. What if we wanted to change multiple things and not by a linear factor? Let's start by running the flowgraph to make sure we get the output as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else_output.png<br />
<br />
Now we can open up the GRC generated python file if_else.py which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.get_number()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We are only concerned about a couple of parts namely the part where the probe is being read:<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
We can see that the variable '''val''' is obtaining the value of the probe block. We can write our if-else statement here based on the value of '''val''' to change the amplitude and frequency of our sine wave. But how do we change the frequency and amplitude? We can use the part where the '''QT GUI Entry''' updates the flowgraph. For the variable freq:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
</syntaxhighlight><br />
<br />
and for the variable ampl:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
</syntaxhighlight><br />
We can see that the functions set_ampl and set_freq can be used for just that - setting the amplitude and the frequency. Thus we can go back and modify our probe function with the if-else statement to give power to our friend.<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_funct ion_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
<br />
Now there is one more thing we need to take care of. GRC has compiled the python file in the order of creation of the elements, which was okay as long as there were no crossreferences. With the introduced adaptation (calling set_ampl and set_freq inside the _variable_function_probe_0_probe()) we need to fix the order of declarations. As set_ampl and set_freq both modify parameters of analog_sig_source_x_0_0 but analog_sig_source_x_0_0 is not instantiated before line 150, we have to move the declaration of the _variable_function_probe_0_probe() and everything related below that.<br />
<br />
<syntaxhighlight lang="python"><br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
</syntaxhighlight><br />
<br />
Full code copied below:<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: if_else<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from PyQt5 import Qt<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
from gnuradio.filter import firdes<br />
import sip<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import gr<br />
import sys<br />
import signal<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
import time<br />
import threading<br />
from gnuradio import qtgui<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "if_else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("if_else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel('freq' + ": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel('ampl' + ": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
"", #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
<br />
labels = ['Signal 1', 'Signal 2', 'Signal 3', 'Signal 4', 'Signal 5',<br />
'Signal 6', 'Signal 7', 'Signal 8', 'Signal 9', 'Signal 10']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ['blue', 'red', 'green', 'black', 'cyan',<br />
'magenta', 'yellow', 'dark red', 'dark green', 'dark blue']<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
<br />
<br />
for i in range(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_0_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, freq, ampl, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0, 0)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
<br />
val = self.probe.level()<br />
print (val)<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_0_0, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_0_0.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_0_0.set_amplitude(self.ampl)<br />
<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We can then simply run our flowgraph from outside of GRC using<br />
<br />
<pre><br />
$ python3 if_else_mod.py<br />
</pre><br />
We should be able to see the numbers 0 and 1 on the terminal and the sine wave changing amplitude and frequency as the numbers change.<br />
<br />
This tutorial is merely an introduction on using python in GNU Radio, for a more advanced tutorial see [[TutorialsWritePythonApplications]] .<br />
<br />
== 3.2. Where Do Blocks Come From? ==<br />
<br />
Now that we have covered some of the ways we can modify our GRC generated Python files, we can see how to make our own blocks to add functionality to suit our specific project needs. Let us start off simple to get a feel for the waters. We will be making a block that is able to multiply a signal by the number we specify. The first thing we need to realize is that GNU Radio comes with gr_modtool, a utility that makes creating OOT Modules easy. Let us open up a terminal and begin in a project folder for the tutorial three.<br />
<br />
=== 3.2.1. Using gr_modtool ===<br />
<br />
Before we begin, we need to figure out what the commands for gr_modtool are so let's ask for help.<br />
<br />
<pre>$ gr_modtool help<br />
Usage:<br />
gr_modtool [options] -- Run with the given options.<br />
gr_modtool help -- Show a list of commands.<br />
gr_modtool help -- Shows the help for a given command.<br />
<br />
List of possible commands:<br />
<br />
Name Aliases Description<br />
=====================================================================<br />
disable dis Disable block (comments out CMake entries for files)<br />
info getinfo,inf Return information about a given module<br />
remove rm,del Remove block (delete files and remove Makefile entries)<br />
makexml mx Make XML file for GRC block bindings<br />
add insert Add block to the out-of-tree module.<br />
newmod nm,create Create a new out-of-tree module<br />
</pre><br />
We immediately see there are many commands available. In this tutorial we will only cover '''newmod''' and '''add'''; however, the thorough explanation should enable smooth usage of the other gr_modtool commands without guidance.<br />
<br />
First, we notice that in addition to getting help seeing the commands we can use, we can also request more information on individual commands. Let us start with newmod as that is the command to create a new out-of-tree module.<br />
<br />
<pre><br />
$ gr_modtool help newmod<br />
Usage: gr_modtool nm [options].<br />
Call gr_modtool without any options to run it interactively.<br />
<br />
Options:<br />
General options:<br />
-h, --help Displays this help message.<br />
-d DIRECTORY, --directory=DIRECTORY<br />
Base directory of the module. Defaults to the cwd.<br />
-n MODULE_NAME, --module-name=MODULE_NAME<br />
Use this to override the current module's name (is<br />
normally autodetected).<br />
-N BLOCK_NAME, --block-name=BLOCK_NAME<br />
Name of the block, where applicable.<br />
--skip-lib Don't do anything in the lib/ subdirectory.<br />
--skip-swig Don't do anything in the swig/ subdirectory.<br />
--skip-Python Don't do anything in the Python/ subdirectory.<br />
--skip-grc Don't do anything in the grc/ subdirectory.<br />
--scm-mode=SCM_MODE<br />
Use source control management (yes, no or auto).<br />
-y, --yes Answer all questions with 'yes'. This can overwrite<br />
and delete your files, so be careful.<br />
<br />
New out-of-tree module options:<br />
--srcdir=SRCDIR Source directory for the module template<br />
</pre><br />
Now that we have read over the list of commands for newmod, we can deduce that the one we want to pick is -n which is the default so we can simply type the MODULE_NAME after newmod. It is actually advised to avoid using "-n" as for other commands it can override the auto-detected name. For now, let's ignore the other options.<br />
<br />
=== 3.2.2. Setting up a new block ===<br />
<br />
<pre><br />
$ gr_modtool newmod tutorial<br />
Creating out-of-tree module in ./gr-tutorial... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.<br />
</pre><br />
We should now see a new folder, gr-tutorial, in our current directory. Let's examine the folder to figure out what gr_modtool has done for us.<br />
<br />
<pre><br />
gr-tutorial$ ls<br />
apps cmake CMakeLists.txt docs examples grc include lib Python swig<br />
</pre><br />
Since we are dealing with Python in this tutorial we only need to concern ourselves with the Python folder and the grc folder. Before we can dive into code, we need to create a block from a template. There are actually four different types of Python blocks, however it's a little too soon to discuss that. We will dive into the synchronous 1:1 input to output block to make explaining things easier (this is a block that outputs as many items as it gets as input, but don't worry about this right now).<br />
<br />
Now we know the language we want to write our block in (Python) and the type of block (synchronous block) we can now add a block to our module. Again, we should run the gr_modtool help command until we are familiar with the different commands. We see that the '''add''' command is what we are looking for. Now we need to run help on the add command in order to see what we need to enter given the information we have so far.<br />
<br />
<pre><br />
gr-tutorial$ gr_modtool help add<br />
... (General Options from Last Help)<br />
Add module options:<br />
-t BLOCK_TYPE, --block-type=BLOCK_TYPE<br />
One of sink, source, sync, decimator, interpolator,<br />
general, tagged_stream, hier, noblock.<br />
--license-file=LICENSE_FILE<br />
File containing the license header for every source<br />
code file.<br />
--argument-list=ARGUMENT_LIST<br />
The argument list for the constructor and make<br />
functions.<br />
--add-Python-qa If given, Python QA code is automatically added if<br />
possible.<br />
--add-cpp-qa If given, C++ QA code is automatically added if<br />
possible.<br />
--skip-cmakefiles If given, only source files are written, but<br />
CMakeLists.txt files are left unchanged.<br />
-l LANG, --lang=LANG<br />
Language (cpp or Python)<br />
</pre><br />
We can see the '''-l LANG''' and the '''-t BLOCK_TYPE''' are relevant for our example. Thus when creating our new block, we know the command. When prompted for a name simply enter "multiply_py_ff", when prompted for an argument list enter "multiple", and when prompted for Python QA (Quality Assurance) code type "y", or just hit enter (the capital letter is the default value).<br />
<br />
<pre>gr-tutorial$ gr_modtool add -t sync -l python<br />
GNU Radio module name identified: tutorial<br />
Language: Python<br />
Enter name of block/code (without module name prefix): multiply_py_ff<br />
Block/code identifier: multiply_py_ff<br />
Enter valid argument list, including default arguments: multiple<br />
Add Python QA code? [Y/n] y<br />
Adding file 'Python/multiply_py_ff.py'...<br />
Adding file 'Python/qa_multiply_py_ff.py'...<br />
Editing Python/CMakeLists.txt...<br />
Adding file 'grc/tutorial_multiply_py_ff.xml'...<br />
Editing grc/CMakeLists.txt...<br />
</pre><br />
We notice 5 changes: Two changes to CMakeLists.txt files, one new file <code>qa_multiply_py_ff.py</code> which is used to test our code, one new file <code>multiply_py_ff.py</code> which is the functional part, and one new file <code>tutorial_multiply_py_ff.xml</code>, which is used to link the block to the GRC. Again all this happens in the Python and grc subfolders.<br />
<br />
==== 3.2.2.1. What's with the <code>_ff</code>? ====<br />
<br />
For blocks with strict types, we use suffixes to declare the input and output types. This block operates on floats, so we give it the suffix <code>_ff</code>: Float in, float out. Other suffixes are <code>_cc</code> (complex in, complex out), or simply <code>_f</code> (a sink or source with no in- or outputs that uses floats). For a more detailed description, see the [[FAQ]] or the [[BlocksCodingGuide]].<br />
<br />
=== 3.2.3. Modifying the Python Block File ===<br />
<br />
Let's begin with the multiply_py_ff.py file found in the Python folder. Opening it without any changes gives the following:<br />
<br />
<syntaxhighlight lang="python"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class multiply_py_ff(gr.sync_block):<br />
"""<br />
docstring for block multiply_py_ff<br />
"""<br />
def __init__(self, multiple):<br />
gr.sync_block.__init__(self,<br />
name="multiply_py_ff",<br />
in_sig=[<+numpy.float+>],<br />
out_sig=[<+numpy.float+>])<br />
self.multiple = multiple<br />
<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
Let's take this one line by line as our first Python examples. We are already familiar with the imports so we will skip those lines. We are familiar with the constructor (init) of Python so can immediately see that if we want to use our variable "multiple", we need to add another line. Let us not forget to preserve those spaces as some code editors like to add tabs to new lines. How do we use the variable multiple?<br />
<br />
How to use variable multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, multiple):<br />
self.multiple = multiple<br />
gr.sync_block.__init__(self,<br />
</syntaxhighlight><br />
<br />
<br />
We notice that there are "&lt;''...''&gt;" scattered in many places. These placeholders are from gr_modtool and tell us where we need to alter things<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[<+numpy.float+>]<br />
out_sig=[<+numpy.float+>]<br />
</syntaxhighlight><br />
The '''gr.sync_block.''init''''' takes in 4 inputs: self, name, and the size/type of the input and output vectors. First, we want to make the item size a single precision float or numpy.float32 by removing the "&lt;''" and the "''&gt;". If we wanted vectors, we could define those as in_sig=[(numpy.float32,4),numpy.float32]. This means there are two input ports, one for vectors of 4 floats and the other for scalars. It is worth noting that if in_sig contains nothing then it becomes a source block, and if out_sig contains nothing it becomes a sink block (provided we change return <code>len(output_items[0])</code> to return <code>len(input_items[0])</code> since output_items is empty). Our changes to the first placeholders should appear as follows:<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[numpy.float32]<br />
out_sig=[numpy.float32]<br />
</syntaxhighlight><br />
The other piece of code that has the placeholders is in the work function but let us first get a better understanding of the work function:<br />
<br />
<syntaxhighlight lang="python"><br />
def work(self, input_items, output_items)<br />
</syntaxhighlight><br />
The work function is where the actual processing happens, where we want our code to be. Because this is a sync block, the number of input items always equals the number of output items because synchronous block ensures a fixed output to input rate. There are also decimation and interpolation blocks where the number of output items are a user specified multiple of the number of input items. We will further discuss when to use what block type in the third section of this tutorial. For now we look at the placeholder:<br />
<br />
<syntaxhighlight lang="python"><br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
The "in0" and "out" simply store the input and output in a variable to make the block easier to write. The signal processing can be anything including if statements, loops, function calls but for this example we only need to modify the out[:] = in0 line so that our input signal is multiplied by our variable multiple. What do we need to add to make the in0 multiply by our multiple?<br />
<br />
How to Multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
out[:] = in0*self.multiple<br />
</syntaxhighlight><br />
<br />
That's it! Our block should now be able to multiply but to be sure we have these things called Quality Assurance tests!<br />
<br />
=== 3.2.4. QA Tests ===<br />
<br />
Now we need to test it to make sure it will run correctly when we install it unto GNU Radio. This is a very important step and we should never forget to include tests with our code! Let us open up qa_multiply_py_ff.py which is copied below:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
from multiply_py_ff import multiply_py_ff<br />
<br />
class qa_multiply_py_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
# set up fg<br />
self.tb.run ()<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_multiply_py_ff, "qa_multiply_py_ff.xml")<br />
</syntaxhighlight><br />
gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. The only part we need to worry about is the def test_001_t function. We know we need input data so let us create data. We want it to be in the form of a vector so that we can test multiple values at once. Let us create a vector of floats<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
</syntaxhighlight><br />
We also need output data so we can compare the input of the block to ensure that it is doing what we expect it to do. Let us multiply by 2 for simplicity.<br />
<br />
<syntaxhighlight lang="python">expected_result = (0, 2, -4, 11, -1)<br />
</syntaxhighlight><br />
Now we can create a flowgraph as we have when we first introduced using Python in GNU Radio. We can use the blocks library specifically the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__source__f.html vector_source_f] function and the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__sink__f.html vector_sink_f] function which are linked to the doxygen manual which we should be able to read and understand. Let us assign three variables "src", "mult", and "snk" to the blocks. The first is shown below:<br />
<br />
<syntaxhighlight lang="python">src = blocks.vector_source_f(src_data)</syntaxhighlight><br />
The rest are hidden below as an exercise:<br />
<br />
What do we assign snk and mult?<br />
<br />
<syntaxhighlight lang="python">mult = multiply_py_ff(2)<br />
snk = blocks.vector_sink_f()<br />
</syntaxhighlight><br />
<br />
<br />
Now we need to connect everything as src <s>&gt; mult</s>&gt; snk. Instead of using self.connect as we did in our other blocks we need to use self.tb.connect because of the setUp function. Below is how we would connect the src block to the mult block.<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (src, mult)<br />
</syntaxhighlight><br />
<br />
How would we connect the other blocks together?<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (mult, snk)<br />
</syntaxhighlight><br />
<br />
<br />
Then we can run the graph and store the data from the sink as below:<br />
<br />
<syntaxhighlight lang="python">self.tb.run ()<br />
result_data = snk.data ()<br />
</syntaxhighlight><br />
Lastly we can run our comparison function that will tell us whether the numbers match up to 6 decimal places. We are using the assertFloatTuplesAlmostEqual instead of the "regular assert functions"https://docs.python.org/2/library/unittest.html#assert-methods included in python's unittest because there may be situations where we cannot get a=b due to rounding in floating point numbers.<br />
<br />
<syntaxhighlight lang="python"><br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
All together the new test_001_t function should appear as below:<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
expected_result = (0, 2, -4, 11, -1)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
snk = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, snk)<br />
self.tb.run ()<br />
result_data = snk.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
<br />
We can then go to the python directory and run:<br />
<br />
<pre><br />
gr-tutorial/python$ python qa_multiply_py_ff.py<br />
.<br />
----------------------------------------------------------------------<br />
Ran 1 test in 0.004s<br />
<br />
OK<br />
</pre><br />
<br />
While we are here, we should take a break to change one of the numbers in the src_data to ensure that the block is actually checking the values and to simply see what an error looks like. Python allows for really quick testing of blocks without having to compile anything; simply change something and re-run the QA test.<br />
<br />
=== 3.2.5. XML Files ===<br />
<br />
At this point we should have written a Python block and a QA test for that block. The next thing to do is edit the XML file in the grc folder so that we can get another step closer to using it in GRC. GRC uses the XML files for all the options we see. We actually don't need to write any Python or C++ code to have a block display in GRC but of course if we would connect it, it wouldn't do anything but give errors. We should get out of the python folder and go to the grc folder where all the XML files reside. There is a tool in gr_modtool called makexml but it is only available for C++ blocks. Let us open the tutorial_multiply_py_ff.xml file copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>multiply_py_ff</name><br />
<key>tutorial_multiply_py_ff</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.multiply_py_ff($multiple)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>...</name><br />
<key>...</key><br />
<type>...</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<source><br />
<name>out</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</source><br />
</block></syntaxhighlight><br />
<br />
We can change the name that appears and the category it will appear in GRC. The category is where the block will be found in GRC. Examples of categories tag are '''Audio''' and '''Waveform Generators''' used in previous examples. Examples of names tag are the '''QT GUI Time Sink''' or the '''Audio Sink'''. Again, we can go through the file and find the modtool place holders. The first is copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
</syntaxhighlight><br />
This is referring to the parameter that we used in the very beginning when creating our block: the variable called "multiple". We can fill it in as below:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<param><br />
<name>Multiple</name><br />
<key>multiple</key><br />
<type>float</type><br />
</param><br />
</syntaxhighlight><br />
The next placeholder can be found in the sink and source tags:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
</syntaxhighlight><br />
We can see that it is asking for a type so we can simply erase everything in the tag and replace it with "float" for both the source and the sink blocks. That should do it for this block. The best way to get more experience writing xml files is to look at the source code of previously made blocks such as the existing multiple block. Let's go back to this and use the documentation tag!<br />
<br />
=== 3.2.6. Installing Python Blocks ===<br />
<br />
Now that we have edited the XML file; we are ready to install the block into the GRC. First, we need to get out of the /grc directory and create a build directory called "build". Inside the build directory, we can then run a series of commands:<br />
<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
<br />
We should then open up the GRC and take a look at the new block.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_blockname.png<br />
<br />
We can see the category and the block name. When we drag it into the GRC workspace, we can see the multiple variable we set in the param tag of the XML file.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_param.png<br />
<br />
Now that we can see our block, let us test it to make sure it works. Below is an example of one of the many ways to check whether it is actually multiplying.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_2.png<br />
<br />
== 3.3. My QPSK Demodulator for Satellites ==<br />
<br />
Now that we have a better understanding on how to add new blocks to GNU Radio for use in the GRC, we can do another example. This time we will be creating a QPSK demodulator block using the same process as before in the same module. Let's first setup the scenario. There is a "noise source" that outputs data in complex float format but let's pretend it comes from a satellite being aimed at our computer. Our secret agent insider tells us this particular satellite encodes digital data using QPSK modulation. We can decode this using a QPSK demodulator that outputs data into bytes. Our insider tells us the space manual doesn't specify whether it's gray code or not. We want to read the bytes using a time sink. What would our flowgraph look like?<br />
<br />
Incomplete Flowgraph...<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_3.png<br />
<br />
<br />
Now that we know the input type, output type, and parameters, we can ask the question we skipped with our multiply_py_ff block. What type of block do we want?<br />
<br />
=== 3.3.1. Choosing a Block Type ===<br />
<br />
The GNU Radio scheduler optimizes for throughput (as opposed to latency). To do this it needs to know the number of samples each block is inputting and outputting thus came the creation of decimation and interpolation blocks where we specify the multiple factor. This is directly related to the sampling rate discussion we had in tutorial 2 where these special types of blocks are able to change the sampling rate to something else in order to comply with a specific piece of hardware such as our soundcard or a specific modulation/demodulation.<br />
<br />
* Synchronous (1:1) - Number of items at input port equals the number of items at output port. An example is the multiply_py_ff block. <br />
* Decimation (N:1) - Number of input items is a fixed multiple of the number of output items. Examples include filters such as decimating high pass and low pass filters (e.g., for sampling rate conversion).<br />
* Interpolation (1:M) - Number of output items is a fixed multiple of the number of input items. An example is the Interpolating FIR Filter.<br />
* General/Basic (N:M) - Provides no relation between the number of input items and the number of output items. An example is the rational resampler block which can be either a sync, decimator, or interpolator.<br />
<br />
When we insert data into our block, do we need more samples or less samples? Or put another way, should our sample rate change?<br />
<br />
What Type of Block Should we Use?<br />
* Sync Block or Basic Block<br />
<br />
<br />
=== 3.3.2. Adding Another Block to our OOT Module ===<br />
<br />
Now we know everything we need to know to create the block in gr_modtool. As a refresher, what would our gr_modtool command be?<br />
<br />
gr_modtool command...<br />
<pre><br />
gr-tutorial$ gr_modtool add -t sync -l python<br />
<br />
name: qpsk_demod_py_cb (c for complex input and b for byte/char/int8 output)<br /><br />
args: gray_code<br /><br />
QA Code: y<br />
</pre><br />
<br />
<br />
Now we have our files setup so we can begin with editing the qpsk_demod_py.py file in the /python folder<br />
<br />
=== 3.3.3. The QPSK Demodulator ===<br />
<br />
The code here is a little more than we have done before so let's take it in steps. First what do we expect our block to do? We can start with a constellation plot which shows the four different quadrants and their mappings to bit 0 and bit 1<br />
<br />
With [http://en.wikipedia.org/wiki/Phase-shift_keying#Quadrature_phase-shift_keying_.28QPSK.29 Gray coding] (adjacent only flips by 1). Note that the mapping we use here is different from the mapping on wikipedia:<br />
<br />
<br />
(-1+1j) 10 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 11 | 01 (1-1j)<br />
<br />
Without Gray coding:<br />
<br />
(-1+1j) 11 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 10 | 01 (1-1j)<br />
<br />
We can see that we will need to write an if-else statement to take into account the gray_code variable. We will also need four other if-else statements inside the main if-else so that we can pick the mapping. Our pseudocode will look as follows:<br />
<br />
if gray_code<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "11" = 3<br />
else<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "10" = 2<br />
<br />
So we have everything we need to implement. Let's go ahead and fill in our gr_modtoool placeholders. We can begin with def init. There are three changes. How do we use the variable gray_code outside the function (similar to what we did with multiple in the last example)? What are the input and output types in [http://docs.scipy.org/doc/numpy/user/basics.types.html? numpy]<br />
<br />
Changes to def ''init''...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
</syntaxhighlight><br />
<br />
Once we have our constructor setup, we can go onto the work function. For simplicity and beauty, let us call the pseudocode we made above a function "get_minimum_distance" that takes samples as input arguments. In our multiply_py_ff example, we took all the samples and multiplied them with with out[:] = in0*self.multiple. The in0 is actually a vector so contains many samples within it. The multiply example required the same operation for each sample so it was okay to simply operate on the entire vector but now we need to have different operations per sample so what do we do?<br />
<br />
How can we operate on samples in a vector?<br />
* loops!<br />
<br />
<syntaxhighlight lang="python"><br />
for i in range(0, len(output_items[0])):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
</syntaxhighlight><br />
<br />
<br />
Now we can move onto the get_minimum_distances(self, sample) function. We already have pseudo code so the next step is translating to Python. Below is a snip of what the code can look like. Again there are multiple ways to do this<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
</syntaxhighlight><br />
Let us try to fill in the other cases for gray code and non-gray code. Below is what the entire file Python file can look like once complete:<br />
<br />
qpsk_demod_py_cb.py<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class qpsk_demod_py(gr.sync_block):<br />
"""<br />
docstring for block qpsk_demod_py<br />
"""<br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
<br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 3 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
else:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 3 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 2 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
<br />
for i in range(0, len(in0)):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
<br />
<br />
Now that we have code, we know what's next!<br />
<br />
=== 3.3.4. Multiple QA Tests ===<br />
<br />
We can test our qpsk_demod_py for when it is in gray_code mode and when it's not in gray_code mode. To do that we need to setup multiple tests in our single QA file. QA tests generally follow the same setup from before. We select some inputs as tests and we check them against what we expect the outputs to be. The only difference from the multiply qa test is that this qa test requires more cases. There are four quadrants we need to map and two modes so in total there are eight test cases. We can open up our qa_qpsk_demod_py_ff.py file to change things.<br />
<br />
We can copy the def test_001_t from the qa_multiply_py_ff code which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def test_001_t (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (-6, 8, -11, 4, 6)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
This time we are working with a complex input so our src = blocks.vector_source_f must change. If we use the search bar in the manual we can find the other options:<br />
<br />
PIC of SEARCH<br />
<br />
b - bytes/unsigned char/int8<br /><br />
c - complex<br /><br />
f - float<br /><br />
i - int<br /><br />
s - short<br />
<br />
What do we change our source and sink vectors to?<br /><br />
src = blocks.vector_source_c (src_data)<br /><br />
dst = blocks.vector_sink_b ()<br /><br />
<br />
<br />
Before we move onto actual test cases, let us decide which mode we are testing for the test_001_t. We can create a new variable and assign it False (translates to 0) to test non-Gray code<br />
<br />
<syntaxhighlight lang="python"><br />
gray_code = False<br />
</syntaxhighlight><br />
Once we know we want to test non gray_code mappings, we can refer to our chart above and start placing in the proper inputs and outputs into the src_data and the expected_results. For instance if we were testing only two cases for non gray_code, we would do:<br />
<br />
<syntaxhighlight lang="python" line="line">src_data = ((-1-1j), (-1+1j))<br />
expected_result = (2, 3)<br />
</syntaxhighlight><br />
Last thing to do is call upon our new block in the "qpsk =" line and pass it the gray_code parameter<br />
<br />
qpsk = ?<br />
* qpsk = qpsk_demod_py_cb (gray_code)<br />
<br />
Now that we are done with the non gray_code test, we can simply create another test "def test_002_t (self):" and copy the contents underneath making sure that for this test we set gray_code = True and change the expected_result so it matches gray_code mapping. The full test is copied below:<br />
<br />
Full QA QPSK Demod Code...<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import numpy<br />
from qpsk_demod_py_cb import qpsk_demod_py<br />
<br />
class qa_qpsk_demod (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
gray_code = False<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (2, 3, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
def test_002_t (self):<br />
gray_code = True<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (3, 2, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qpsk_demod_py_cb.xml")<br />
</syntaxhighlight><br />
<br />
<br />
We can then run the test in Python and all should say something similar to:<br />
<br />
Ran 2 tests in 0.005s<br />
<br />
OK<br />
<br />
So once we verify it works as expected, we can then edit our XML file so that it is usable inside GRC.<br />
<br />
=== 3.3.5. XML Mods, Installation, and Running ===<br />
<br />
This XML is very similar to the XML file for the multiply_py_ff block so all we need to do is set the gray_code parameter and pick the correct input (complex) and output (byte) types. A copy of the full XML file is below:<br />
<br />
XML File for QPSK Demod<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>qpsk_demod_py</name><br />
<key>tutorial_qpsk_demod_py</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.qpsk_demod_py($gray_code)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<type>int</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<!-- e.g. int, float, complex, byte, short, xxx_vector, ...--><br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</block><br />
<br />
</syntaxhighlight><br />
<br />
<br />
We can then install as we did for the multiply block however we need to rerun cmake in order to take into account the new block:<br />
<pre><br />
cd build<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
</pre><br />
Then we can open up our GRC file from the beginning and place our missing block we just made.<br />
<br />
What is the Expected Output?<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_4.png<br />
<br />
== 3.4. Conclusion ==<br />
<br />
And that is it for now with Python. Let us know your thoughts before going on to the [[Guided_Tutorial_GNU_Radio_in_C++|C++ tutorial]].<br />
<br />
=== 3.4.1. Questions We Should Now be Able to Answer ===<br />
<br />
1. How do we set input- and output signatures in Python blocks?<br /><br />
2. Consider this I/O signature: (FIXME). Which input types and output types does it take?<br />
<br />
=== 3.4.2. Links to Further Information ===<br />
<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/BlocksCodingGuide Blocks Coding Guide]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModules Out-of-Tree Modules]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/TutorialsWritePythonApplications Writing Python Applications]<br />
<br />
== 3.5. Candidates for Future Sections ==<br />
<br />
Possible topics we may want to add depending on feedback and questions on the mailing list<br /><br />
- How to add documentation to blocks<br /><br />
- Constructor arguments, History, Output Multiple<br />
<br />
<br />
-----<br />
<br />
&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GNU_Radio_in_Python&diff=6418Guided Tutorial GNU Radio in Python2019-12-14T09:31:28Z<p>Cmrincon: </p>
<hr />
<div>&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
<br />
= Tutorial: Working with GNU Radio in Python =<br />
<br />
== Objectives ==<br />
<br />
* Python Blocks<br />
* OOT modules make the actual apps / functionality (GR is the API!)<br />
* How to add OOTs<br />
* How to add Python blocks with gr_modtool and how to code them<br />
* QPSK mapping<br />
* How to add GRC bindings for block<br />
<br />
== Prerequisites ==<br />
<br />
* Working Installation of GNU Radio 3.8 or later<br />
* [[Guided_Tutorial_GRC|GRC Tutorial]] (Recommended)<br />
* Familiar with Python<br />
<br />
-----<br />
<br />
== 3.1. Intro to Using GNU Radio with Python ==<br />
<br />
This tutorial goes through three parts. The first is how to modify, create, or simply understand the Python generated files GRC produces for us. The second is how to create our own custom out-of-tree (OOT) modules from the ground up. Lastly we go through an actual project to get more practice and build intuition on how we can use GNU Radio in our own project. As with the last tutorial, all the content - pictures, source code, and grc files - is included in the [https://github.com/gnuradio/gr-tutorial gr-tutorial repository] which we should have a local copy if we followed the directions from the [[Guided_Tutorial_GRC|GRC Tutorial]]<br />
<br />
Again we should have a directory with the solutions and a directory with our work as below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
/home/user/gnuradio/tutorials/solutions<br />
/home/user/gnuradio/tutorials/work<br />
</syntaxhighlight><br />
<br />
As a rule, if we hover over the GRC flowgraph images, we will be able to see the corresponding filename. Same applies for other images. Full code files are collapsed with the filename in the collapsed handle.<br />
<br />
=== 3.1.1. GRC Generated Python Files ===<br />
<br />
Let us look at a dial-tone example on the GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png<br />
<br />
When we click the '''Generate''' button, the terminal tells us it produced a .py file so let's open that to examine its code which is reproduced below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: tutorial_three_1<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
from gnuradio.filter import firdes<br />
import sys<br />
import signal<br />
from PyQt5 import Qt<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
<br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("tutorial_three_1")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.samp_rate = samp_rate = 32000<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.audio_sink_0 = audio.sink(samp_rate, '', True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
<br />
<br />
<br />
def main(top_block_cls=tutorial_three_1, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
The first thing for us to realize is that the GRC can generate Python files that we can then modify to do things we wouldn't be able to do in GNU Radio Companion such as perform [[TutorialsSimulations|simulations]]. The libraries available in Python open up a whole new realm of possibilities! For now, we will explore the structure of the GRC Python files so we are comfortable creating more interesting applications.<br />
<br />
=== 3.1.2. Hello World Dissected ===<br />
<br />
While examining the code, we need to get familiar with documentation. GNU Radio uses Doxygen (the software) for the [http://gnuradio.org/doc/doxygen/ GNU Radio Manual]. The easiest way to go through the documentation is to go through the functions that we use so let us simplify our code by only including the bare bones needed to run the dial-tone example.<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env Python3<br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, "")<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass<br />
<br />
</syntaxhighlight><br />
<br />
Let us examine this line by line:<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
</syntaxhighlight><br />
Tells the shell that this file is a Python file and to use the Python interpreter to run this file. Should always be included at the top to run from the terminal.<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
</syntaxhighlight><br />
<br />
Tells Python the modules to include. We must always have '''gr''' to run GNU Radio applications. The audio sink is included in the audio module and the signal_source_f is included in the analog module which is why we include them. [http://legacy.python.org/dev/peps/pep-0008/ PEP8] tells us we should import every module on its own line.<br />
<br />
<syntaxhighlight lang="python"><br />
class my_top_block(gr.top_block):<br />
</syntaxhighlight><br />
Define a class called "my_top_block" which is derived from another class, '''gr.top_block'''. This class is basically a container for the flow graph. By deriving from gr.top_block, we get all the hooks and functions we need to add blocks and interconnect them.<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self):<br />
</syntaxhighlight><br />
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.<br />
<br />
<syntaxhighlight lang="python"><br />
gr.top_block.__init__(self)<br />
</syntaxhighlight><br />
The parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br />
<br />
<syntaxhighlight lang="python"><br />
sample_rate = 32000<br />
ampl = 0.1<br />
</syntaxhighlight><br />
Variable declarations for sampling rate and amplitude that we will later use.<br />
<br />
=== 3.1.3. A Look at Documentation ===<br />
<br />
<syntaxhighlight lang="python"><br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
</syntaxhighlight><br />
Here we are using functions from GNU Radio so let's have a look at the documentation for '''analog.sig_source_f''' which is available in [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html the GNU Radio manual]. We can find it easily by using the search function as below:<br />
<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_search.png<br />
<br />
We can then scroll down to '''Member Function Documentation''' to see how the function is used and the parameters it accepts as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_source.png<br />
<br />
We can see that our function '''analog.sig_source_f''' takes in 5 parameters but in our code we are only using 4. There is no error because the last input '''offset''' is set to "0" by default as shown in the documentation. The first input is the '''sampling_freq''' which we defined as '''sample_rate''' in our code. The second input is asking for a '''gr::analog::gr_waveform_t''' waveform so let's click that [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177 link] to find out more.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_waveform.png<br />
<br />
We can see that there are a couple of options to choose from. In this case we chose '''analog.GR_SIN_WAVE'''. The third input is '''wave_freq''' which we input "350" or "440". The fourth input is '''ampl''' which we defined as '''ampl'''.<br />
<br />
<syntaxhighlight lang="python"><br />
dst = audio.sink(sample_rate, "")<br />
</syntaxhighlight><br />
Because documentation is so important, let's look at another example. Again, we can look at the documentation for '''audio.sink''' which is available on the GNU Radio Manual through the search function:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink.png<br />
<br />
We can then as before scroll down to the '''Member Function Documentation''' to see the parameters it accepts:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink_detail.png<br />
<br />
This time we have 3 inputs with the last being optional. In our code, for the first input '''sampling_rate''' we used are '''sample_rate''' variable. In the second input, we have a choice for what device to use for audio output. If we leave it alone as "" then it'll choose the default on our machine.<br />
<br />
=== 3.1.4. Connecting the Block Together ===<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
</syntaxhighlight><br />
The general syntax for connecting blocks is self.connect(block1, block2, block3, ...) which would connect the output of block1 with the input of block2, the output of block2 with the input of block3 and so on. We can connect as many blocks as we wish with one connect() call. However this only work when there is a one-to-one correspondence. If we go back to our initial flowgraph, there are 2 inputs to the '''Audio Sink''' block. The way to connect them is by using the syntax above. The first line connects the only output of src0 (350 Hz waveform) to the first input of dst (Audio Sink). The second line connects the only output of src1 (440 Hz waveform) to the second input of dst (Audio Sink). The code so far is equivalent to the flowgraph we have created in the beginning; the rest of the lines simply start the flowgraph and provide a keyboard interrupt.<br />
<br />
<syntaxhighlight lang="python"><br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except KeyboardInterrupt:<br />
pass<br />
</syntaxhighlight><br />
<br />
Luckily we are past the early years of GNU Radio when there was no GRC to make the Python files for us. Nowadays we can simply click things together in GRC instead of having to write code in Python to build flowgraphs. Still, a good understanding of what is going on every time we run GRC is good to know as it gives us more control of what we want the program to do.<br />
<br />
=== 3.1.5. Modifying the GRC Generated Python File ===<br />
<br />
For instance, what if we wanted to change variables such as frequency and amplitude when a certain event occurs. How do we implement if statements, state machines, etc in GNU Radio? One way is to create our own blocks which we will delve into at length later. Another is to modify our GRC generated python file.<br />
<br />
Our friend heard we were into RF so being cheap he has asked us to power his house using RF. He wants us to give him high power during the day so he can watch TV and play video games while at night give him low power so he power his alarm clock to wake up for work in the morning. We first need to setup the clock which keeps track of the day and gives us 1 for day or 0 for night. Once we get the time we can send him power through a sine wave using our massive terawatt amplifier and massive dish in our backyard. We did the calculations and we want to pulse at 1kHz, 1 amplitude during the day and 100Hz, 0.3 amplitude at night. Here's what we came up with in GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else.png<br />
<br />
<span style="color:blue">- '''Frequency''' to "freq", '''Amplitude''' to "ampl"</span><br /><br />
<span style="color:red">- '''ID''' to "probe"</span><br /><br />
- Everything else is visible<br />
<br />
The top section keeps track of time and will switch from 0 to 1 while the bottom section sends the pulse. The problem we encounter however is that there is no if-statement block. Sure we can tie the probe to the frequency as we did in tutorial2 for the singing sine wave but that only allows changing by a factor. What if we wanted to change multiple things and not by a linear factor? Let's start by running the flowgraph to make sure we get the output as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else_output.png<br />
<br />
Now we can open up the GRC generated python file if_else.py which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python2<br />
# -*- coding: utf-8 -*-<br />
##################################################<br />
# GNU Radio Python Flow Graph<br />
# Title: If Else<br />
# Generated: Thu Sep 13 11:39:57 2018<br />
##################################################<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print "Warning: failed to XInitThreads()"<br />
<br />
from PyQt4 import Qt<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import eng_notation<br />
from gnuradio import gr<br />
from gnuradio import qtgui<br />
from gnuradio.eng_option import eng_option<br />
from gnuradio.filter import firdes<br />
from optparse import OptionParser<br />
import sip<br />
import sys<br />
import threading<br />
import time<br />
from gnuradio import qtgui<br />
<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "If Else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("If Else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel("freq"+": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel("ampl"+": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
'QT GUI Plot', #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(-1, True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
if not True:<br />
self.qtgui_time_sink_x_0.disable_legend()<br />
<br />
labels = ['', '', '', '', '',<br />
'', '', '', '', '']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ["blue", "red", "green", "black", "cyan",<br />
"magenta", "yellow", "dark red", "dark green", "blue"]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
<br />
for i in xrange(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_1.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_1.set_amplitude(self.ampl)<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
from distutils.version import StrictVersion<br />
if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We are only concerned about a couple of parts namely the part where the probe is being read:<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
We can see that the variable '''val''' is obtaining the value of the probe block. We can write our if-else statement here based on the value of '''val''' to change the amplitude and frequency of our sine wave. But how do we change the frequency and amplitude? We can use the part where the '''QT GUI Entry''' updates the flowgraph. For the variable freq:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_1.set_frequency(self.freq)<br />
</syntaxhighlight><br />
<br />
and for the variable ampl:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_1.set_amplitude(self.ampl)<br />
</syntaxhighlight><br />
We can see that the functions set_ampl and set_freq can be used for just that - setting the amplitude and the frequency. Thus we can go back and modify our probe function with the if-else statement to give power to our friend.<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
<br />
Now there is one more thing we need to take care of. GRC has compiled the python file in the order of creation of the elements, which was okay as long as there were no crossreferences. With the introduced adaptation (calling set_ampl and set_freq inside the _variable_function_probe_0_probe()) we need to fix the order of declarations. As set_ampl and set_freq both modify parameters of analog_sig_source_x_1 but analog_sig_source_x_1 is not instantiated before line 150, we have to move the declaration of the _variable_function_probe_0_probe() and everything related below that.<br />
<br />
<syntaxhighlight lang="python"><br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
</syntaxhighlight><br />
<br />
Full code copied below:<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python2<br />
# -*- coding: utf-8 -*-<br />
##################################################<br />
# GNU Radio Python Flow Graph<br />
# Title: If Else<br />
# Generated: Thu Sep 13 11:39:57 2018<br />
##################################################<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print "Warning: failed to XInitThreads()"<br />
<br />
from PyQt4 import Qt<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import eng_notation<br />
from gnuradio import gr<br />
from gnuradio import qtgui<br />
from gnuradio.eng_option import eng_option<br />
from gnuradio.filter import firdes<br />
from optparse import OptionParser<br />
import sip<br />
import sys<br />
import threading<br />
import time<br />
from gnuradio import qtgui<br />
<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "If Else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("If Else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel("freq"+": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel("ampl"+": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
'QT GUI Plot', #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(-1, True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
if not True:<br />
self.qtgui_time_sink_x_0.disable_legend()<br />
<br />
labels = ['', '', '', '', '',<br />
'', '', '', '', '']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ["blue", "red", "green", "black", "cyan",<br />
"magenta", "yellow", "dark red", "dark green", "blue"]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
<br />
for i in xrange(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_1.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_1.set_amplitude(self.ampl)<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
from distutils.version import StrictVersion<br />
if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We can then simply run our flowgraph from outside of GRC using<br />
<br />
<pre><br />
$ python if_else_mod.py<br />
</pre><br />
We should be able to see the numbers 0 and 1 on the terminal and the sine wave changing amplitude and frequency as the numbers change.<br />
<br />
This tutorial is merely an introduction on using python in GNU Radio, for a more advanced tutorial see [[TutorialsWritePythonApplications]] .<br />
<br />
== 3.2. Where Do Blocks Come From? ==<br />
<br />
Now that we have covered some of the ways we can modify our GRC generated Python files, we can see how to make our own blocks to add functionality to suit our specific project needs. Let us start off simple to get a feel for the waters. We will be making a block that is able to multiply a signal by the number we specify. The first thing we need to realize is that GNU Radio comes with gr_modtool, a utility that makes creating OOT Modules easy. Let us open up a terminal and begin in a project folder for the tutorial three.<br />
<br />
=== 3.2.1. Using gr_modtool ===<br />
<br />
Before we begin, we need to figure out what the commands for gr_modtool are so let's ask for help.<br />
<br />
<pre>$ gr_modtool help<br />
Usage:<br />
gr_modtool [options] -- Run with the given options.<br />
gr_modtool help -- Show a list of commands.<br />
gr_modtool help -- Shows the help for a given command.<br />
<br />
List of possible commands:<br />
<br />
Name Aliases Description<br />
=====================================================================<br />
disable dis Disable block (comments out CMake entries for files)<br />
info getinfo,inf Return information about a given module<br />
remove rm,del Remove block (delete files and remove Makefile entries)<br />
makexml mx Make XML file for GRC block bindings<br />
add insert Add block to the out-of-tree module.<br />
newmod nm,create Create a new out-of-tree module<br />
</pre><br />
We immediately see there are many commands available. In this tutorial we will only cover '''newmod''' and '''add'''; however, the thorough explanation should enable smooth usage of the other gr_modtool commands without guidance.<br />
<br />
First, we notice that in addition to getting help seeing the commands we can use, we can also request more information on individual commands. Let us start with newmod as that is the command to create a new out-of-tree module.<br />
<br />
<pre><br />
$ gr_modtool help newmod<br />
Usage: gr_modtool nm [options].<br />
Call gr_modtool without any options to run it interactively.<br />
<br />
Options:<br />
General options:<br />
-h, --help Displays this help message.<br />
-d DIRECTORY, --directory=DIRECTORY<br />
Base directory of the module. Defaults to the cwd.<br />
-n MODULE_NAME, --module-name=MODULE_NAME<br />
Use this to override the current module's name (is<br />
normally autodetected).<br />
-N BLOCK_NAME, --block-name=BLOCK_NAME<br />
Name of the block, where applicable.<br />
--skip-lib Don't do anything in the lib/ subdirectory.<br />
--skip-swig Don't do anything in the swig/ subdirectory.<br />
--skip-Python Don't do anything in the Python/ subdirectory.<br />
--skip-grc Don't do anything in the grc/ subdirectory.<br />
--scm-mode=SCM_MODE<br />
Use source control management (yes, no or auto).<br />
-y, --yes Answer all questions with 'yes'. This can overwrite<br />
and delete your files, so be careful.<br />
<br />
New out-of-tree module options:<br />
--srcdir=SRCDIR Source directory for the module template<br />
</pre><br />
Now that we have read over the list of commands for newmod, we can deduce that the one we want to pick is -n which is the default so we can simply type the MODULE_NAME after newmod. It is actually advised to avoid using "-n" as for other commands it can override the auto-detected name. For now, let's ignore the other options.<br />
<br />
=== 3.2.2. Setting up a new block ===<br />
<br />
<pre><br />
$ gr_modtool newmod tutorial<br />
Creating out-of-tree module in ./gr-tutorial... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.<br />
</pre><br />
We should now see a new folder, gr-tutorial, in our current directory. Let's examine the folder to figure out what gr_modtool has done for us.<br />
<br />
<pre><br />
gr-tutorial$ ls<br />
apps cmake CMakeLists.txt docs examples grc include lib Python swig<br />
</pre><br />
Since we are dealing with Python in this tutorial we only need to concern ourselves with the Python folder and the grc folder. Before we can dive into code, we need to create a block from a template. There are actually four different types of Python blocks, however it's a little too soon to discuss that. We will dive into the synchronous 1:1 input to output block to make explaining things easier (this is a block that outputs as many items as it gets as input, but don't worry about this right now).<br />
<br />
Now we know the language we want to write our block in (Python) and the type of block (synchronous block) we can now add a block to our module. Again, we should run the gr_modtool help command until we are familiar with the different commands. We see that the '''add''' command is what we are looking for. Now we need to run help on the add command in order to see what we need to enter given the information we have so far.<br />
<br />
<pre><br />
gr-tutorial$ gr_modtool help add<br />
... (General Options from Last Help)<br />
Add module options:<br />
-t BLOCK_TYPE, --block-type=BLOCK_TYPE<br />
One of sink, source, sync, decimator, interpolator,<br />
general, tagged_stream, hier, noblock.<br />
--license-file=LICENSE_FILE<br />
File containing the license header for every source<br />
code file.<br />
--argument-list=ARGUMENT_LIST<br />
The argument list for the constructor and make<br />
functions.<br />
--add-Python-qa If given, Python QA code is automatically added if<br />
possible.<br />
--add-cpp-qa If given, C++ QA code is automatically added if<br />
possible.<br />
--skip-cmakefiles If given, only source files are written, but<br />
CMakeLists.txt files are left unchanged.<br />
-l LANG, --lang=LANG<br />
Language (cpp or Python)<br />
</pre><br />
We can see the '''-l LANG''' and the '''-t BLOCK_TYPE''' are relevant for our example. Thus when creating our new block, we know the command. When prompted for a name simply enter "multiply_py_ff", when prompted for an argument list enter "multiple", and when prompted for Python QA (Quality Assurance) code type "y", or just hit enter (the capital letter is the default value).<br />
<br />
<pre>gr-tutorial$ gr_modtool add -t sync -l python<br />
GNU Radio module name identified: tutorial<br />
Language: Python<br />
Enter name of block/code (without module name prefix): multiply_py_ff<br />
Block/code identifier: multiply_py_ff<br />
Enter valid argument list, including default arguments: multiple<br />
Add Python QA code? [Y/n] y<br />
Adding file 'Python/multiply_py_ff.py'...<br />
Adding file 'Python/qa_multiply_py_ff.py'...<br />
Editing Python/CMakeLists.txt...<br />
Adding file 'grc/tutorial_multiply_py_ff.xml'...<br />
Editing grc/CMakeLists.txt...<br />
</pre><br />
We notice 5 changes: Two changes to CMakeLists.txt files, one new file <code>qa_multiply_py_ff.py</code> which is used to test our code, one new file <code>multiply_py_ff.py</code> which is the functional part, and one new file <code>tutorial_multiply_py_ff.xml</code>, which is used to link the block to the GRC. Again all this happens in the Python and grc subfolders.<br />
<br />
==== 3.2.2.1. What's with the <code>_ff</code>? ====<br />
<br />
For blocks with strict types, we use suffixes to declare the input and output types. This block operates on floats, so we give it the suffix <code>_ff</code>: Float in, float out. Other suffixes are <code>_cc</code> (complex in, complex out), or simply <code>_f</code> (a sink or source with no in- or outputs that uses floats). For a more detailed description, see the [[FAQ]] or the [[BlocksCodingGuide]].<br />
<br />
=== 3.2.3. Modifying the Python Block File ===<br />
<br />
Let's begin with the multiply_py_ff.py file found in the Python folder. Opening it without any changes gives the following:<br />
<br />
<syntaxhighlight lang="python"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class multiply_py_ff(gr.sync_block):<br />
"""<br />
docstring for block multiply_py_ff<br />
"""<br />
def __init__(self, multiple):<br />
gr.sync_block.__init__(self,<br />
name="multiply_py_ff",<br />
in_sig=[<+numpy.float+>],<br />
out_sig=[<+numpy.float+>])<br />
self.multiple = multiple<br />
<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
Let's take this one line by line as our first Python examples. We are already familiar with the imports so we will skip those lines. We are familiar with the constructor (init) of Python so can immediately see that if we want to use our variable "multiple", we need to add another line. Let us not forget to preserve those spaces as some code editors like to add tabs to new lines. How do we use the variable multiple?<br />
<br />
How to use variable multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, multiple):<br />
self.multiple = multiple<br />
gr.sync_block.__init__(self,<br />
</syntaxhighlight><br />
<br />
<br />
We notice that there are "&lt;''...''&gt;" scattered in many places. These placeholders are from gr_modtool and tell us where we need to alter things<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[<+numpy.float+>]<br />
out_sig=[<+numpy.float+>]<br />
</syntaxhighlight><br />
The '''gr.sync_block.''init''''' takes in 4 inputs: self, name, and the size/type of the input and output vectors. First, we want to make the item size a single precision float or numpy.float32 by removing the "&lt;''" and the "''&gt;". If we wanted vectors, we could define those as in_sig=[(numpy.float32,4),numpy.float32]. This means there are two input ports, one for vectors of 4 floats and the other for scalars. It is worth noting that if in_sig contains nothing then it becomes a source block, and if out_sig contains nothing it becomes a sink block (provided we change return <code>len(output_items[0])</code> to return <code>len(input_items[0])</code> since output_items is empty). Our changes to the first placeholders should appear as follows:<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[numpy.float32]<br />
out_sig=[numpy.float32]<br />
</syntaxhighlight><br />
The other piece of code that has the placeholders is in the work function but let us first get a better understanding of the work function:<br />
<br />
<syntaxhighlight lang="python"><br />
def work(self, input_items, output_items)<br />
</syntaxhighlight><br />
The work function is where the actual processing happens, where we want our code to be. Because this is a sync block, the number of input items always equals the number of output items because synchronous block ensures a fixed output to input rate. There are also decimation and interpolation blocks where the number of output items are a user specified multiple of the number of input items. We will further discuss when to use what block type in the third section of this tutorial. For now we look at the placeholder:<br />
<br />
<syntaxhighlight lang="python"><br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
The "in0" and "out" simply store the input and output in a variable to make the block easier to write. The signal processing can be anything including if statements, loops, function calls but for this example we only need to modify the out[:] = in0 line so that our input signal is multiplied by our variable multiple. What do we need to add to make the in0 multiply by our multiple?<br />
<br />
How to Multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
out[:] = in0*self.multiple<br />
</syntaxhighlight><br />
<br />
That's it! Our block should now be able to multiply but to be sure we have these things called Quality Assurance tests!<br />
<br />
=== 3.2.4. QA Tests ===<br />
<br />
Now we need to test it to make sure it will run correctly when we install it unto GNU Radio. This is a very important step and we should never forget to include tests with our code! Let us open up qa_multiply_py_ff.py which is copied below:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
from multiply_py_ff import multiply_py_ff<br />
<br />
class qa_multiply_py_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
# set up fg<br />
self.tb.run ()<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_multiply_py_ff, "qa_multiply_py_ff.xml")<br />
</syntaxhighlight><br />
gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. The only part we need to worry about is the def test_001_t function. We know we need input data so let us create data. We want it to be in the form of a vector so that we can test multiple values at once. Let us create a vector of floats<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
</syntaxhighlight><br />
We also need output data so we can compare the input of the block to ensure that it is doing what we expect it to do. Let us multiply by 2 for simplicity.<br />
<br />
<syntaxhighlight lang="python">expected_result = (0, 2, -4, 11, -1)<br />
</syntaxhighlight><br />
Now we can create a flowgraph as we have when we first introduced using Python in GNU Radio. We can use the blocks library specifically the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__source__f.html vector_source_f] function and the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__sink__f.html vector_sink_f] function which are linked to the doxygen manual which we should be able to read and understand. Let us assign three variables "src", "mult", and "snk" to the blocks. The first is shown below:<br />
<br />
<syntaxhighlight lang="python">src = blocks.vector_source_f(src_data)</syntaxhighlight><br />
The rest are hidden below as an exercise:<br />
<br />
What do we assign snk and mult?<br />
<br />
<syntaxhighlight lang="python">mult = multiply_py_ff(2)<br />
snk = blocks.vector_sink_f()<br />
</syntaxhighlight><br />
<br />
<br />
Now we need to connect everything as src <s>&gt; mult</s>&gt; snk. Instead of using self.connect as we did in our other blocks we need to use self.tb.connect because of the setUp function. Below is how we would connect the src block to the mult block.<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (src, mult)<br />
</syntaxhighlight><br />
<br />
How would we connect the other blocks together?<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (mult, snk)<br />
</syntaxhighlight><br />
<br />
<br />
Then we can run the graph and store the data from the sink as below:<br />
<br />
<syntaxhighlight lang="python">self.tb.run ()<br />
result_data = snk.data ()<br />
</syntaxhighlight><br />
Lastly we can run our comparison function that will tell us whether the numbers match up to 6 decimal places. We are using the assertFloatTuplesAlmostEqual instead of the "regular assert functions"https://docs.python.org/2/library/unittest.html#assert-methods included in python's unittest because there may be situations where we cannot get a=b due to rounding in floating point numbers.<br />
<br />
<syntaxhighlight lang="python"><br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
All together the new test_001_t function should appear as below:<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
expected_result = (0, 2, -4, 11, -1)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
snk = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, snk)<br />
self.tb.run ()<br />
result_data = snk.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
<br />
We can then go to the python directory and run:<br />
<br />
<pre><br />
gr-tutorial/python$ python qa_multiply_py_ff.py<br />
.<br />
----------------------------------------------------------------------<br />
Ran 1 test in 0.004s<br />
<br />
OK<br />
</pre><br />
<br />
While we are here, we should take a break to change one of the numbers in the src_data to ensure that the block is actually checking the values and to simply see what an error looks like. Python allows for really quick testing of blocks without having to compile anything; simply change something and re-run the QA test.<br />
<br />
=== 3.2.5. XML Files ===<br />
<br />
At this point we should have written a Python block and a QA test for that block. The next thing to do is edit the XML file in the grc folder so that we can get another step closer to using it in GRC. GRC uses the XML files for all the options we see. We actually don't need to write any Python or C++ code to have a block display in GRC but of course if we would connect it, it wouldn't do anything but give errors. We should get out of the python folder and go to the grc folder where all the XML files reside. There is a tool in gr_modtool called makexml but it is only available for C++ blocks. Let us open the tutorial_multiply_py_ff.xml file copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>multiply_py_ff</name><br />
<key>tutorial_multiply_py_ff</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.multiply_py_ff($multiple)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>...</name><br />
<key>...</key><br />
<type>...</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<source><br />
<name>out</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</source><br />
</block></syntaxhighlight><br />
<br />
We can change the name that appears and the category it will appear in GRC. The category is where the block will be found in GRC. Examples of categories tag are '''Audio''' and '''Waveform Generators''' used in previous examples. Examples of names tag are the '''QT GUI Time Sink''' or the '''Audio Sink'''. Again, we can go through the file and find the modtool place holders. The first is copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
</syntaxhighlight><br />
This is referring to the parameter that we used in the very beginning when creating our block: the variable called "multiple". We can fill it in as below:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<param><br />
<name>Multiple</name><br />
<key>multiple</key><br />
<type>float</type><br />
</param><br />
</syntaxhighlight><br />
The next placeholder can be found in the sink and source tags:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
</syntaxhighlight><br />
We can see that it is asking for a type so we can simply erase everything in the tag and replace it with "float" for both the source and the sink blocks. That should do it for this block. The best way to get more experience writing xml files is to look at the source code of previously made blocks such as the existing multiple block. Let's go back to this and use the documentation tag!<br />
<br />
=== 3.2.6. Installing Python Blocks ===<br />
<br />
Now that we have edited the XML file; we are ready to install the block into the GRC. First, we need to get out of the /grc directory and create a build directory called "build". Inside the build directory, we can then run a series of commands:<br />
<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
<br />
We should then open up the GRC and take a look at the new block.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_blockname.png<br />
<br />
We can see the category and the block name. When we drag it into the GRC workspace, we can see the multiple variable we set in the param tag of the XML file.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_param.png<br />
<br />
Now that we can see our block, let us test it to make sure it works. Below is an example of one of the many ways to check whether it is actually multiplying.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_2.png<br />
<br />
== 3.3. My QPSK Demodulator for Satellites ==<br />
<br />
Now that we have a better understanding on how to add new blocks to GNU Radio for use in the GRC, we can do another example. This time we will be creating a QPSK demodulator block using the same process as before in the same module. Let's first setup the scenario. There is a "noise source" that outputs data in complex float format but let's pretend it comes from a satellite being aimed at our computer. Our secret agent insider tells us this particular satellite encodes digital data using QPSK modulation. We can decode this using a QPSK demodulator that outputs data into bytes. Our insider tells us the space manual doesn't specify whether it's gray code or not. We want to read the bytes using a time sink. What would our flowgraph look like?<br />
<br />
Incomplete Flowgraph...<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_3.png<br />
<br />
<br />
Now that we know the input type, output type, and parameters, we can ask the question we skipped with our multiply_py_ff block. What type of block do we want?<br />
<br />
=== 3.3.1. Choosing a Block Type ===<br />
<br />
The GNU Radio scheduler optimizes for throughput (as opposed to latency). To do this it needs to know the number of samples each block is inputting and outputting thus came the creation of decimation and interpolation blocks where we specify the multiple factor. This is directly related to the sampling rate discussion we had in tutorial 2 where these special types of blocks are able to change the sampling rate to something else in order to comply with a specific piece of hardware such as our soundcard or a specific modulation/demodulation.<br />
<br />
* Synchronous (1:1) - Number of items at input port equals the number of items at output port. An example is the multiply_py_ff block. <br />
* Decimation (N:1) - Number of input items is a fixed multiple of the number of output items. Examples include filters such as decimating high pass and low pass filters (e.g., for sampling rate conversion).<br />
* Interpolation (1:M) - Number of output items is a fixed multiple of the number of input items. An example is the Interpolating FIR Filter.<br />
* General/Basic (N:M) - Provides no relation between the number of input items and the number of output items. An example is the rational resampler block which can be either a sync, decimator, or interpolator.<br />
<br />
When we insert data into our block, do we need more samples or less samples? Or put another way, should our sample rate change?<br />
<br />
What Type of Block Should we Use?<br />
* Sync Block or Basic Block<br />
<br />
<br />
=== 3.3.2. Adding Another Block to our OOT Module ===<br />
<br />
Now we know everything we need to know to create the block in gr_modtool. As a refresher, what would our gr_modtool command be?<br />
<br />
gr_modtool command...<br />
<pre><br />
gr-tutorial$ gr_modtool add -t sync -l python<br />
<br />
name: qpsk_demod_py_cb (c for complex input and b for byte/char/int8 output)<br /><br />
args: gray_code<br /><br />
QA Code: y<br />
</pre><br />
<br />
<br />
Now we have our files setup so we can begin with editing the qpsk_demod_py.py file in the /python folder<br />
<br />
=== 3.3.3. The QPSK Demodulator ===<br />
<br />
The code here is a little more than we have done before so let's take it in steps. First what do we expect our block to do? We can start with a constellation plot which shows the four different quadrants and their mappings to bit 0 and bit 1<br />
<br />
With [http://en.wikipedia.org/wiki/Phase-shift_keying#Quadrature_phase-shift_keying_.28QPSK.29 Gray coding] (adjacent only flips by 1). Note that the mapping we use here is different from the mapping on wikipedia:<br />
<br />
<br />
(-1+1j) 10 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 11 | 01 (1-1j)<br />
<br />
Without Gray coding:<br />
<br />
(-1+1j) 11 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 10 | 01 (1-1j)<br />
<br />
We can see that we will need to write an if-else statement to take into account the gray_code variable. We will also need four other if-else statements inside the main if-else so that we can pick the mapping. Our pseudocode will look as follows:<br />
<br />
if gray_code<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "11" = 3<br />
else<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "10" = 2<br />
<br />
So we have everything we need to implement. Let's go ahead and fill in our gr_modtoool placeholders. We can begin with def init. There are three changes. How do we use the variable gray_code outside the function (similar to what we did with multiple in the last example)? What are the input and output types in [http://docs.scipy.org/doc/numpy/user/basics.types.html? numpy]<br />
<br />
Changes to def ''init''...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
</syntaxhighlight><br />
<br />
Once we have our constructor setup, we can go onto the work function. For simplicity and beauty, let us call the pseudocode we made above a function "get_minimum_distance" that takes samples as input arguments. In our multiply_py_ff example, we took all the samples and multiplied them with with out[:] = in0*self.multiple. The in0 is actually a vector so contains many samples within it. The multiply example required the same operation for each sample so it was okay to simply operate on the entire vector but now we need to have different operations per sample so what do we do?<br />
<br />
How can we operate on samples in a vector?<br />
* loops!<br />
<br />
<syntaxhighlight lang="python"><br />
for i in range(0, len(output_items[0])):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
</syntaxhighlight><br />
<br />
<br />
Now we can move onto the get_minimum_distances(self, sample) function. We already have pseudo code so the next step is translating to Python. Below is a snip of what the code can look like. Again there are multiple ways to do this<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
</syntaxhighlight><br />
Let us try to fill in the other cases for gray code and non-gray code. Below is what the entire file Python file can look like once complete:<br />
<br />
qpsk_demod_py_cb.py<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class qpsk_demod_py(gr.sync_block):<br />
"""<br />
docstring for block qpsk_demod_py<br />
"""<br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
<br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 3 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
else:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 3 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 2 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
<br />
for i in range(0, len(in0)):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
<br />
<br />
Now that we have code, we know what's next!<br />
<br />
=== 3.3.4. Multiple QA Tests ===<br />
<br />
We can test our qpsk_demod_py for when it is in gray_code mode and when it's not in gray_code mode. To do that we need to setup multiple tests in our single QA file. QA tests generally follow the same setup from before. We select some inputs as tests and we check them against what we expect the outputs to be. The only difference from the multiply qa test is that this qa test requires more cases. There are four quadrants we need to map and two modes so in total there are eight test cases. We can open up our qa_qpsk_demod_py_ff.py file to change things.<br />
<br />
We can copy the def test_001_t from the qa_multiply_py_ff code which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def test_001_t (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (-6, 8, -11, 4, 6)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
This time we are working with a complex input so our src = blocks.vector_source_f must change. If we use the search bar in the manual we can find the other options:<br />
<br />
PIC of SEARCH<br />
<br />
b - bytes/unsigned char/int8<br /><br />
c - complex<br /><br />
f - float<br /><br />
i - int<br /><br />
s - short<br />
<br />
What do we change our source and sink vectors to?<br /><br />
src = blocks.vector_source_c (src_data)<br /><br />
dst = blocks.vector_sink_b ()<br /><br />
<br />
<br />
Before we move onto actual test cases, let us decide which mode we are testing for the test_001_t. We can create a new variable and assign it False (translates to 0) to test non-Gray code<br />
<br />
<syntaxhighlight lang="python"><br />
gray_code = False<br />
</syntaxhighlight><br />
Once we know we want to test non gray_code mappings, we can refer to our chart above and start placing in the proper inputs and outputs into the src_data and the expected_results. For instance if we were testing only two cases for non gray_code, we would do:<br />
<br />
<syntaxhighlight lang="python" line="line">src_data = ((-1-1j), (-1+1j))<br />
expected_result = (2, 3)<br />
</syntaxhighlight><br />
Last thing to do is call upon our new block in the "qpsk =" line and pass it the gray_code parameter<br />
<br />
qpsk = ?<br />
* qpsk = qpsk_demod_py_cb (gray_code)<br />
<br />
Now that we are done with the non gray_code test, we can simply create another test "def test_002_t (self):" and copy the contents underneath making sure that for this test we set gray_code = True and change the expected_result so it matches gray_code mapping. The full test is copied below:<br />
<br />
Full QA QPSK Demod Code...<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import numpy<br />
from qpsk_demod_py_cb import qpsk_demod_py<br />
<br />
class qa_qpsk_demod (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
gray_code = False<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (2, 3, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
def test_002_t (self):<br />
gray_code = True<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (3, 2, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qpsk_demod_py_cb.xml")<br />
</syntaxhighlight><br />
<br />
<br />
We can then run the test in Python and all should say something similar to:<br />
<br />
Ran 2 tests in 0.005s<br />
<br />
OK<br />
<br />
So once we verify it works as expected, we can then edit our XML file so that it is usable inside GRC.<br />
<br />
=== 3.3.5. XML Mods, Installation, and Running ===<br />
<br />
This XML is very similar to the XML file for the multiply_py_ff block so all we need to do is set the gray_code parameter and pick the correct input (complex) and output (byte) types. A copy of the full XML file is below:<br />
<br />
XML File for QPSK Demod<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>qpsk_demod_py</name><br />
<key>tutorial_qpsk_demod_py</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.qpsk_demod_py($gray_code)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<type>int</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<!-- e.g. int, float, complex, byte, short, xxx_vector, ...--><br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</block><br />
<br />
</syntaxhighlight><br />
<br />
<br />
We can then install as we did for the multiply block however we need to rerun cmake in order to take into account the new block:<br />
<pre><br />
cd build<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
</pre><br />
Then we can open up our GRC file from the beginning and place our missing block we just made.<br />
<br />
What is the Expected Output?<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_4.png<br />
<br />
== 3.4. Conclusion ==<br />
<br />
And that is it for now with Python. Let us know your thoughts before going on to the [[Guided_Tutorial_GNU_Radio_in_C++|C++ tutorial]].<br />
<br />
=== 3.4.1. Questions We Should Now be Able to Answer ===<br />
<br />
1. How do we set input- and output signatures in Python blocks?<br /><br />
2. Consider this I/O signature: (FIXME). Which input types and output types does it take?<br />
<br />
=== 3.4.2. Links to Further Information ===<br />
<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/BlocksCodingGuide Blocks Coding Guide]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModules Out-of-Tree Modules]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/TutorialsWritePythonApplications Writing Python Applications]<br />
<br />
== 3.5. Candidates for Future Sections ==<br />
<br />
Possible topics we may want to add depending on feedback and questions on the mailing list<br /><br />
- How to add documentation to blocks<br /><br />
- Constructor arguments, History, Output Multiple<br />
<br />
<br />
-----<br />
<br />
&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GNU_Radio_in_Python&diff=6417Guided Tutorial GNU Radio in Python2019-12-14T08:40:43Z<p>Cmrincon: update to v3.8.0.0</p>
<hr />
<div>&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
<br />
= Tutorial: Working with GNU Radio in Python =<br />
<br />
== Objectives ==<br />
<br />
* Python Blocks<br />
* OOT modules make the actual apps / functionality (GR is the API!)<br />
* How to add OOTs<br />
* How to add Python blocks with gr_modtool and how to code them<br />
* QPSK mapping<br />
* How to add GRC bindings for block<br />
<br />
== Prerequisites ==<br />
<br />
* Working Installation of GNU Radio 3.7.4 or later<br />
* [[Guided_Tutorial_GRC|GRC Tutorial]] (Recommended)<br />
* Familiar with Python<br />
<br />
-----<br />
<br />
== 3.1. Intro to Using GNU Radio with Python ==<br />
<br />
This tutorial goes through three parts. The first is how to modify, create, or simply understand the Python generated files GRC produces for us. The second is how to create our own custom out-of-tree (OOT) modules from the ground up. Lastly we go through an actual project to get more practice and build intuition on how we can use GNU Radio in our own project. As with the last tutorial, all the content - pictures, source code, and grc files - is included in the [https://github.com/gnuradio/gr-tutorial gr-tutorial repository] which we should have a local copy if we followed the directions from the [[Guided_Tutorial_GRC|GRC Tutorial]]<br />
<br />
Again we should have a directory with the solutions and a directory with our work as below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
/home/user/gnuradio/tutorials/solutions<br />
/home/user/gnuradio/tutorials/work<br />
</syntaxhighlight><br />
<br />
As a rule, if we hover over the GRC flowgraph images, we will be able to see the corresponding filename. Same applies for other images. Full code files are collapsed with the filename in the collapsed handle.<br />
<br />
=== 3.1.1. GRC Generated Python Files ===<br />
<br />
Let us look at a dial-tone example on the GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png<br />
<br />
When we click the '''Generate''' button, the terminal tells us it produced a .py file so let's open that to examine its code which is reproduced below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: tutorial_three_1<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
from gnuradio.filter import firdes<br />
import sys<br />
import signal<br />
from PyQt5 import Qt<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
<br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("tutorial_three_1")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.samp_rate = samp_rate = 32000<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.audio_sink_0 = audio.sink(samp_rate, '', True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
<br />
<br />
<br />
def main(top_block_cls=tutorial_three_1, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
The first thing for us to realize is that the GRC can generate Python files that we can then modify to do things we wouldn't be able to do in GNU Radio Companion such as perform [[TutorialsSimulations|simulations]]. The libraries available in Python open up a whole new realm of possibilities! For now, we will explore the structure of the GRC Python files so we are comfortable creating more interesting applications.<br />
<br />
=== 3.1.2. Hello World Dissected ===<br />
<br />
While examining the code, we need to get familiar with documentation. GNU Radio uses Doxygen (the software) for the [http://gnuradio.org/doc/doxygen/ GNU Radio Manual]. The easiest way to go through the documentation is to go through the functions that we use so let us simplify our code by only including the bare bones needed to run the dial-tone example.<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env Python3<br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, "")<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass<br />
<br />
</syntaxhighlight><br />
<br />
Let us examine this line by line:<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python3<br />
</syntaxhighlight><br />
Tells the shell that this file is a Python file and to use the Python interpreter to run this file. Should always be included at the top to run from the terminal.<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
</syntaxhighlight><br />
<br />
Tells Python the modules to include. We must always have '''gr''' to run GNU Radio applications. The audio sink is included in the audio module and the signal_source_f is included in the analog module which is why we include them. [http://legacy.python.org/dev/peps/pep-0008/ PEP8] tells us we should import every module on its own line.<br />
<br />
<syntaxhighlight lang="python"><br />
class my_top_block(gr.top_block):<br />
</syntaxhighlight><br />
Define a class called "my_top_block" which is derived from another class, '''gr.top_block'''. This class is basically a container for the flow graph. By deriving from gr.top_block, we get all the hooks and functions we need to add blocks and interconnect them.<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self):<br />
</syntaxhighlight><br />
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.<br />
<br />
<syntaxhighlight lang="python"><br />
gr.top_block.__init__(self)<br />
</syntaxhighlight><br />
The parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br />
<br />
<syntaxhighlight lang="python"><br />
sample_rate = 32000<br />
ampl = 0.1<br />
</syntaxhighlight><br />
Variable declarations for sampling rate and amplitude that we will later use.<br />
<br />
=== 3.1.3. A Look at Documentation ===<br />
<br />
<syntaxhighlight lang="python"><br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
</syntaxhighlight><br />
Here we are using functions from GNU Radio so let's have a look at the documentation for '''analog.sig_source_f''' which is available in [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html the GNU Radio manual]. We can find it easily by using the search function as below:<br />
<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_search.png<br />
<br />
We can then scroll down to '''Member Function Documentation''' to see how the function is used and the parameters it accepts as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_source.png<br />
<br />
We can see that our function '''analog.sig_source_f''' takes in 5 parameters but in our code we are only using 4. There is no error because the last input '''offset''' is set to "0" by default as shown in the documentation. The first input is the '''sampling_freq''' which we defined as '''sample_rate''' in our code. The second input is asking for a '''gr::analog::gr_waveform_t''' waveform so let's click that [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177 link] to find out more.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_waveform.png<br />
<br />
We can see that there are a couple of options to choose from. In this case we chose '''analog.GR_SIN_WAVE'''. The third input is '''wave_freq''' which we input "350" or "440". The fourth input is '''ampl''' which we defined as '''ampl'''.<br />
<br />
<syntaxhighlight lang="python"><br />
dst = audio.sink(sample_rate, "")<br />
</syntaxhighlight><br />
Because documentation is so important, let's look at another example. Again, we can look at the documentation for '''audio.sink''' which is available on the GNU Radio Manual through the search function:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink.png<br />
<br />
We can then as before scroll down to the '''Member Function Documentation''' to see the parameters it accepts:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink_detail.png<br />
<br />
This time we have 3 inputs with the last being optional. In our code, for the first input '''sampling_rate''' we used are '''sample_rate''' variable. In the second input, we have a choice for what device to use for audio output. If we leave it alone as "" then it'll choose the default on our machine.<br />
<br />
=== 3.1.4. Connecting the Block Together ===<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
</syntaxhighlight><br />
The general syntax for connecting blocks is self.connect(block1, block2, block3, ...) which would connect the output of block1 with the input of block2, the output of block2 with the input of block3 and so on. We can connect as many blocks as we wish with one connect() call. However this only work when there is a one-to-one correspondence. If we go back to our initial flowgraph, there are 2 inputs to the '''Audio Sink''' block. The way to connect them is by using the syntax above. The first line connects the only output of src0 (350 Hz waveform) to the first input of dst (Audio Sink). The second line connects the only output of src1 (440 Hz waveform) to the second input of dst (Audio Sink). The code so far is equivalent to the flowgraph we have created in the beginning; the rest of the lines simply start the flowgraph and provide a keyboard interrupt.<br />
<br />
<syntaxhighlight lang="python"><br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except KeyboardInterrupt:<br />
pass<br />
</syntaxhighlight><br />
<br />
Luckily we are past the early years of GNU Radio when there was no GRC to make the Python files for us. Nowadays we can simply click things together in GRC instead of having to write code in Python to build flowgraphs. Still, a good understanding of what is going on every time we run GRC is good to know as it gives us more control of what we want the program to do.<br />
<br />
=== 3.1.5. Modifying the GRC Generated Python File ===<br />
<br />
For instance, what if we wanted to change variables such as frequency and amplitude when a certain event occurs. How do we implement if statements, state machines, etc in GNU Radio? One way is to create our own blocks which we will delve into at length later. Another is to modify our GRC generated python file.<br />
<br />
Our friend heard we were into RF so being cheap he has asked us to power his house using RF. He wants us to give him high power during the day so he can watch TV and play video games while at night give him low power so he power his alarm clock to wake up for work in the morning. We first need to setup the clock which keeps track of the day and gives us 1 for day or 0 for night. Once we get the time we can send him power through a sine wave using our massive terawatt amplifier and massive dish in our backyard. We did the calculations and we want to pulse at 1kHz, 1 amplitude during the day and 100Hz, 0.3 amplitude at night. Here's what we came up with in GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else.png<br />
<br />
<span style="color:blue">- '''Frequency''' to "freq", '''Amplitude''' to "ampl"</span><br /><br />
<span style="color:red">- '''ID''' to "probe"</span><br /><br />
- Everything else is visible<br />
<br />
The top section keeps track of time and will switch from 0 to 1 while the bottom section sends the pulse. The problem we encounter however is that there is no if-statement block. Sure we can tie the probe to the frequency as we did in tutorial2 for the singing sine wave but that only allows changing by a factor. What if we wanted to change multiple things and not by a linear factor? Let's start by running the flowgraph to make sure we get the output as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else_output.png<br />
<br />
Now we can open up the GRC generated python file if_else.py which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python2<br />
# -*- coding: utf-8 -*-<br />
##################################################<br />
# GNU Radio Python Flow Graph<br />
# Title: If Else<br />
# Generated: Thu Sep 13 11:39:57 2018<br />
##################################################<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print "Warning: failed to XInitThreads()"<br />
<br />
from PyQt4 import Qt<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import eng_notation<br />
from gnuradio import gr<br />
from gnuradio import qtgui<br />
from gnuradio.eng_option import eng_option<br />
from gnuradio.filter import firdes<br />
from optparse import OptionParser<br />
import sip<br />
import sys<br />
import threading<br />
import time<br />
from gnuradio import qtgui<br />
<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "If Else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("If Else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel("freq"+": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel("ampl"+": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
'QT GUI Plot', #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(-1, True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
if not True:<br />
self.qtgui_time_sink_x_0.disable_legend()<br />
<br />
labels = ['', '', '', '', '',<br />
'', '', '', '', '']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ["blue", "red", "green", "black", "cyan",<br />
"magenta", "yellow", "dark red", "dark green", "blue"]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
<br />
for i in xrange(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_1.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_1.set_amplitude(self.ampl)<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
from distutils.version import StrictVersion<br />
if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We are only concerned about a couple of parts namely the part where the probe is being read:<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
We can see that the variable '''val''' is obtaining the value of the probe block. We can write our if-else statement here based on the value of '''val''' to change the amplitude and frequency of our sine wave. But how do we change the frequency and amplitude? We can use the part where the '''QT GUI Entry''' updates the flowgraph. For the variable freq:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_1.set_frequency(self.freq)<br />
</syntaxhighlight><br />
<br />
and for the variable ampl:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_1.set_amplitude(self.ampl)<br />
</syntaxhighlight><br />
We can see that the functions set_ampl and set_freq can be used for just that - setting the amplitude and the frequency. Thus we can go back and modify our probe function with the if-else statement to give power to our friend.<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
<br />
Now there is one more thing we need to take care of. GRC has compiled the python file in the order of creation of the elements, which was okay as long as there were no crossreferences. With the introduced adaptation (calling set_ampl and set_freq inside the _variable_function_probe_0_probe()) we need to fix the order of declarations. As set_ampl and set_freq both modify parameters of analog_sig_source_x_1 but analog_sig_source_x_1 is not instantiated before line 150, we have to move the declaration of the _variable_function_probe_0_probe() and everything related below that.<br />
<br />
<syntaxhighlight lang="python"><br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
</syntaxhighlight><br />
<br />
Full code copied below:<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python2<br />
# -*- coding: utf-8 -*-<br />
##################################################<br />
# GNU Radio Python Flow Graph<br />
# Title: If Else<br />
# Generated: Thu Sep 13 11:39:57 2018<br />
##################################################<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print "Warning: failed to XInitThreads()"<br />
<br />
from PyQt4 import Qt<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import eng_notation<br />
from gnuradio import gr<br />
from gnuradio import qtgui<br />
from gnuradio.eng_option import eng_option<br />
from gnuradio.filter import firdes<br />
from optparse import OptionParser<br />
import sip<br />
import sys<br />
import threading<br />
import time<br />
from gnuradio import qtgui<br />
<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "If Else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("If Else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel("freq"+": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel("ampl"+": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
'QT GUI Plot', #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(-1, True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
if not True:<br />
self.qtgui_time_sink_x_0.disable_legend()<br />
<br />
labels = ['', '', '', '', '',<br />
'', '', '', '', '']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ["blue", "red", "green", "black", "cyan",<br />
"magenta", "yellow", "dark red", "dark green", "blue"]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
<br />
for i in xrange(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_1.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_1.set_amplitude(self.ampl)<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
from distutils.version import StrictVersion<br />
if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We can then simply run our flowgraph from outside of GRC using<br />
<br />
<pre><br />
$ python if_else_mod.py<br />
</pre><br />
We should be able to see the numbers 0 and 1 on the terminal and the sine wave changing amplitude and frequency as the numbers change.<br />
<br />
This tutorial is merely an introduction on using python in GNU Radio, for a more advanced tutorial see [[TutorialsWritePythonApplications]] .<br />
<br />
== 3.2. Where Do Blocks Come From? ==<br />
<br />
Now that we have covered some of the ways we can modify our GRC generated Python files, we can see how to make our own blocks to add functionality to suit our specific project needs. Let us start off simple to get a feel for the waters. We will be making a block that is able to multiply a signal by the number we specify. The first thing we need to realize is that GNU Radio comes with gr_modtool, a utility that makes creating OOT Modules easy. Let us open up a terminal and begin in a project folder for the tutorial three.<br />
<br />
=== 3.2.1. Using gr_modtool ===<br />
<br />
Before we begin, we need to figure out what the commands for gr_modtool are so let's ask for help.<br />
<br />
<pre>$ gr_modtool help<br />
Usage:<br />
gr_modtool [options] -- Run with the given options.<br />
gr_modtool help -- Show a list of commands.<br />
gr_modtool help -- Shows the help for a given command.<br />
<br />
List of possible commands:<br />
<br />
Name Aliases Description<br />
=====================================================================<br />
disable dis Disable block (comments out CMake entries for files)<br />
info getinfo,inf Return information about a given module<br />
remove rm,del Remove block (delete files and remove Makefile entries)<br />
makexml mx Make XML file for GRC block bindings<br />
add insert Add block to the out-of-tree module.<br />
newmod nm,create Create a new out-of-tree module<br />
</pre><br />
We immediately see there are many commands available. In this tutorial we will only cover '''newmod''' and '''add'''; however, the thorough explanation should enable smooth usage of the other gr_modtool commands without guidance.<br />
<br />
First, we notice that in addition to getting help seeing the commands we can use, we can also request more information on individual commands. Let us start with newmod as that is the command to create a new out-of-tree module.<br />
<br />
<pre><br />
$ gr_modtool help newmod<br />
Usage: gr_modtool nm [options].<br />
Call gr_modtool without any options to run it interactively.<br />
<br />
Options:<br />
General options:<br />
-h, --help Displays this help message.<br />
-d DIRECTORY, --directory=DIRECTORY<br />
Base directory of the module. Defaults to the cwd.<br />
-n MODULE_NAME, --module-name=MODULE_NAME<br />
Use this to override the current module's name (is<br />
normally autodetected).<br />
-N BLOCK_NAME, --block-name=BLOCK_NAME<br />
Name of the block, where applicable.<br />
--skip-lib Don't do anything in the lib/ subdirectory.<br />
--skip-swig Don't do anything in the swig/ subdirectory.<br />
--skip-Python Don't do anything in the Python/ subdirectory.<br />
--skip-grc Don't do anything in the grc/ subdirectory.<br />
--scm-mode=SCM_MODE<br />
Use source control management (yes, no or auto).<br />
-y, --yes Answer all questions with 'yes'. This can overwrite<br />
and delete your files, so be careful.<br />
<br />
New out-of-tree module options:<br />
--srcdir=SRCDIR Source directory for the module template<br />
</pre><br />
Now that we have read over the list of commands for newmod, we can deduce that the one we want to pick is -n which is the default so we can simply type the MODULE_NAME after newmod. It is actually advised to avoid using "-n" as for other commands it can override the auto-detected name. For now, let's ignore the other options.<br />
<br />
=== 3.2.2. Setting up a new block ===<br />
<br />
<pre><br />
$ gr_modtool newmod tutorial<br />
Creating out-of-tree module in ./gr-tutorial... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.<br />
</pre><br />
We should now see a new folder, gr-tutorial, in our current directory. Let's examine the folder to figure out what gr_modtool has done for us.<br />
<br />
<pre><br />
gr-tutorial$ ls<br />
apps cmake CMakeLists.txt docs examples grc include lib Python swig<br />
</pre><br />
Since we are dealing with Python in this tutorial we only need to concern ourselves with the Python folder and the grc folder. Before we can dive into code, we need to create a block from a template. There are actually four different types of Python blocks, however it's a little too soon to discuss that. We will dive into the synchronous 1:1 input to output block to make explaining things easier (this is a block that outputs as many items as it gets as input, but don't worry about this right now).<br />
<br />
Now we know the language we want to write our block in (Python) and the type of block (synchronous block) we can now add a block to our module. Again, we should run the gr_modtool help command until we are familiar with the different commands. We see that the '''add''' command is what we are looking for. Now we need to run help on the add command in order to see what we need to enter given the information we have so far.<br />
<br />
<pre><br />
gr-tutorial$ gr_modtool help add<br />
... (General Options from Last Help)<br />
Add module options:<br />
-t BLOCK_TYPE, --block-type=BLOCK_TYPE<br />
One of sink, source, sync, decimator, interpolator,<br />
general, tagged_stream, hier, noblock.<br />
--license-file=LICENSE_FILE<br />
File containing the license header for every source<br />
code file.<br />
--argument-list=ARGUMENT_LIST<br />
The argument list for the constructor and make<br />
functions.<br />
--add-Python-qa If given, Python QA code is automatically added if<br />
possible.<br />
--add-cpp-qa If given, C++ QA code is automatically added if<br />
possible.<br />
--skip-cmakefiles If given, only source files are written, but<br />
CMakeLists.txt files are left unchanged.<br />
-l LANG, --lang=LANG<br />
Language (cpp or Python)<br />
</pre><br />
We can see the '''-l LANG''' and the '''-t BLOCK_TYPE''' are relevant for our example. Thus when creating our new block, we know the command. When prompted for a name simply enter "multiply_py_ff", when prompted for an argument list enter "multiple", and when prompted for Python QA (Quality Assurance) code type "y", or just hit enter (the capital letter is the default value).<br />
<br />
<pre>gr-tutorial$ gr_modtool add -t sync -l python<br />
GNU Radio module name identified: tutorial<br />
Language: Python<br />
Enter name of block/code (without module name prefix): multiply_py_ff<br />
Block/code identifier: multiply_py_ff<br />
Enter valid argument list, including default arguments: multiple<br />
Add Python QA code? [Y/n] y<br />
Adding file 'Python/multiply_py_ff.py'...<br />
Adding file 'Python/qa_multiply_py_ff.py'...<br />
Editing Python/CMakeLists.txt...<br />
Adding file 'grc/tutorial_multiply_py_ff.xml'...<br />
Editing grc/CMakeLists.txt...<br />
</pre><br />
We notice 5 changes: Two changes to CMakeLists.txt files, one new file <code>qa_multiply_py_ff.py</code> which is used to test our code, one new file <code>multiply_py_ff.py</code> which is the functional part, and one new file <code>tutorial_multiply_py_ff.xml</code>, which is used to link the block to the GRC. Again all this happens in the Python and grc subfolders.<br />
<br />
==== 3.2.2.1. What's with the <code>_ff</code>? ====<br />
<br />
For blocks with strict types, we use suffixes to declare the input and output types. This block operates on floats, so we give it the suffix <code>_ff</code>: Float in, float out. Other suffixes are <code>_cc</code> (complex in, complex out), or simply <code>_f</code> (a sink or source with no in- or outputs that uses floats). For a more detailed description, see the [[FAQ]] or the [[BlocksCodingGuide]].<br />
<br />
=== 3.2.3. Modifying the Python Block File ===<br />
<br />
Let's begin with the multiply_py_ff.py file found in the Python folder. Opening it without any changes gives the following:<br />
<br />
<syntaxhighlight lang="python"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class multiply_py_ff(gr.sync_block):<br />
"""<br />
docstring for block multiply_py_ff<br />
"""<br />
def __init__(self, multiple):<br />
gr.sync_block.__init__(self,<br />
name="multiply_py_ff",<br />
in_sig=[<+numpy.float+>],<br />
out_sig=[<+numpy.float+>])<br />
self.multiple = multiple<br />
<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
Let's take this one line by line as our first Python examples. We are already familiar with the imports so we will skip those lines. We are familiar with the constructor (init) of Python so can immediately see that if we want to use our variable "multiple", we need to add another line. Let us not forget to preserve those spaces as some code editors like to add tabs to new lines. How do we use the variable multiple?<br />
<br />
How to use variable multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, multiple):<br />
self.multiple = multiple<br />
gr.sync_block.__init__(self,<br />
</syntaxhighlight><br />
<br />
<br />
We notice that there are "&lt;''...''&gt;" scattered in many places. These placeholders are from gr_modtool and tell us where we need to alter things<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[<+numpy.float+>]<br />
out_sig=[<+numpy.float+>]<br />
</syntaxhighlight><br />
The '''gr.sync_block.''init''''' takes in 4 inputs: self, name, and the size/type of the input and output vectors. First, we want to make the item size a single precision float or numpy.float32 by removing the "&lt;''" and the "''&gt;". If we wanted vectors, we could define those as in_sig=[(numpy.float32,4),numpy.float32]. This means there are two input ports, one for vectors of 4 floats and the other for scalars. It is worth noting that if in_sig contains nothing then it becomes a source block, and if out_sig contains nothing it becomes a sink block (provided we change return <code>len(output_items[0])</code> to return <code>len(input_items[0])</code> since output_items is empty). Our changes to the first placeholders should appear as follows:<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[numpy.float32]<br />
out_sig=[numpy.float32]<br />
</syntaxhighlight><br />
The other piece of code that has the placeholders is in the work function but let us first get a better understanding of the work function:<br />
<br />
<syntaxhighlight lang="python"><br />
def work(self, input_items, output_items)<br />
</syntaxhighlight><br />
The work function is where the actual processing happens, where we want our code to be. Because this is a sync block, the number of input items always equals the number of output items because synchronous block ensures a fixed output to input rate. There are also decimation and interpolation blocks where the number of output items are a user specified multiple of the number of input items. We will further discuss when to use what block type in the third section of this tutorial. For now we look at the placeholder:<br />
<br />
<syntaxhighlight lang="python"><br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
The "in0" and "out" simply store the input and output in a variable to make the block easier to write. The signal processing can be anything including if statements, loops, function calls but for this example we only need to modify the out[:] = in0 line so that our input signal is multiplied by our variable multiple. What do we need to add to make the in0 multiply by our multiple?<br />
<br />
How to Multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
out[:] = in0*self.multiple<br />
</syntaxhighlight><br />
<br />
That's it! Our block should now be able to multiply but to be sure we have these things called Quality Assurance tests!<br />
<br />
=== 3.2.4. QA Tests ===<br />
<br />
Now we need to test it to make sure it will run correctly when we install it unto GNU Radio. This is a very important step and we should never forget to include tests with our code! Let us open up qa_multiply_py_ff.py which is copied below:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
from multiply_py_ff import multiply_py_ff<br />
<br />
class qa_multiply_py_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
# set up fg<br />
self.tb.run ()<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_multiply_py_ff, "qa_multiply_py_ff.xml")<br />
</syntaxhighlight><br />
gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. The only part we need to worry about is the def test_001_t function. We know we need input data so let us create data. We want it to be in the form of a vector so that we can test multiple values at once. Let us create a vector of floats<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
</syntaxhighlight><br />
We also need output data so we can compare the input of the block to ensure that it is doing what we expect it to do. Let us multiply by 2 for simplicity.<br />
<br />
<syntaxhighlight lang="python">expected_result = (0, 2, -4, 11, -1)<br />
</syntaxhighlight><br />
Now we can create a flowgraph as we have when we first introduced using Python in GNU Radio. We can use the blocks library specifically the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__source__f.html vector_source_f] function and the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__sink__f.html vector_sink_f] function which are linked to the doxygen manual which we should be able to read and understand. Let us assign three variables "src", "mult", and "snk" to the blocks. The first is shown below:<br />
<br />
<syntaxhighlight lang="python">src = blocks.vector_source_f(src_data)</syntaxhighlight><br />
The rest are hidden below as an exercise:<br />
<br />
What do we assign snk and mult?<br />
<br />
<syntaxhighlight lang="python">mult = multiply_py_ff(2)<br />
snk = blocks.vector_sink_f()<br />
</syntaxhighlight><br />
<br />
<br />
Now we need to connect everything as src <s>&gt; mult</s>&gt; snk. Instead of using self.connect as we did in our other blocks we need to use self.tb.connect because of the setUp function. Below is how we would connect the src block to the mult block.<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (src, mult)<br />
</syntaxhighlight><br />
<br />
How would we connect the other blocks together?<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (mult, snk)<br />
</syntaxhighlight><br />
<br />
<br />
Then we can run the graph and store the data from the sink as below:<br />
<br />
<syntaxhighlight lang="python">self.tb.run ()<br />
result_data = snk.data ()<br />
</syntaxhighlight><br />
Lastly we can run our comparison function that will tell us whether the numbers match up to 6 decimal places. We are using the assertFloatTuplesAlmostEqual instead of the "regular assert functions"https://docs.python.org/2/library/unittest.html#assert-methods included in python's unittest because there may be situations where we cannot get a=b due to rounding in floating point numbers.<br />
<br />
<syntaxhighlight lang="python"><br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
All together the new test_001_t function should appear as below:<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
expected_result = (0, 2, -4, 11, -1)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
snk = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, snk)<br />
self.tb.run ()<br />
result_data = snk.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
<br />
We can then go to the python directory and run:<br />
<br />
<pre><br />
gr-tutorial/python$ python qa_multiply_py_ff.py<br />
.<br />
----------------------------------------------------------------------<br />
Ran 1 test in 0.004s<br />
<br />
OK<br />
</pre><br />
<br />
While we are here, we should take a break to change one of the numbers in the src_data to ensure that the block is actually checking the values and to simply see what an error looks like. Python allows for really quick testing of blocks without having to compile anything; simply change something and re-run the QA test.<br />
<br />
=== 3.2.5. XML Files ===<br />
<br />
At this point we should have written a Python block and a QA test for that block. The next thing to do is edit the XML file in the grc folder so that we can get another step closer to using it in GRC. GRC uses the XML files for all the options we see. We actually don't need to write any Python or C++ code to have a block display in GRC but of course if we would connect it, it wouldn't do anything but give errors. We should get out of the python folder and go to the grc folder where all the XML files reside. There is a tool in gr_modtool called makexml but it is only available for C++ blocks. Let us open the tutorial_multiply_py_ff.xml file copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>multiply_py_ff</name><br />
<key>tutorial_multiply_py_ff</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.multiply_py_ff($multiple)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>...</name><br />
<key>...</key><br />
<type>...</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<source><br />
<name>out</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</source><br />
</block></syntaxhighlight><br />
<br />
We can change the name that appears and the category it will appear in GRC. The category is where the block will be found in GRC. Examples of categories tag are '''Audio''' and '''Waveform Generators''' used in previous examples. Examples of names tag are the '''QT GUI Time Sink''' or the '''Audio Sink'''. Again, we can go through the file and find the modtool place holders. The first is copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
</syntaxhighlight><br />
This is referring to the parameter that we used in the very beginning when creating our block: the variable called "multiple". We can fill it in as below:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<param><br />
<name>Multiple</name><br />
<key>multiple</key><br />
<type>float</type><br />
</param><br />
</syntaxhighlight><br />
The next placeholder can be found in the sink and source tags:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
</syntaxhighlight><br />
We can see that it is asking for a type so we can simply erase everything in the tag and replace it with "float" for both the source and the sink blocks. That should do it for this block. The best way to get more experience writing xml files is to look at the source code of previously made blocks such as the existing multiple block. Let's go back to this and use the documentation tag!<br />
<br />
=== 3.2.6. Installing Python Blocks ===<br />
<br />
Now that we have edited the XML file; we are ready to install the block into the GRC. First, we need to get out of the /grc directory and create a build directory called "build". Inside the build directory, we can then run a series of commands:<br />
<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
<br />
We should then open up the GRC and take a look at the new block.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_blockname.png<br />
<br />
We can see the category and the block name. When we drag it into the GRC workspace, we can see the multiple variable we set in the param tag of the XML file.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_param.png<br />
<br />
Now that we can see our block, let us test it to make sure it works. Below is an example of one of the many ways to check whether it is actually multiplying.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_2.png<br />
<br />
== 3.3. My QPSK Demodulator for Satellites ==<br />
<br />
Now that we have a better understanding on how to add new blocks to GNU Radio for use in the GRC, we can do another example. This time we will be creating a QPSK demodulator block using the same process as before in the same module. Let's first setup the scenario. There is a "noise source" that outputs data in complex float format but let's pretend it comes from a satellite being aimed at our computer. Our secret agent insider tells us this particular satellite encodes digital data using QPSK modulation. We can decode this using a QPSK demodulator that outputs data into bytes. Our insider tells us the space manual doesn't specify whether it's gray code or not. We want to read the bytes using a time sink. What would our flowgraph look like?<br />
<br />
Incomplete Flowgraph...<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_3.png<br />
<br />
<br />
Now that we know the input type, output type, and parameters, we can ask the question we skipped with our multiply_py_ff block. What type of block do we want?<br />
<br />
=== 3.3.1. Choosing a Block Type ===<br />
<br />
The GNU Radio scheduler optimizes for throughput (as opposed to latency). To do this it needs to know the number of samples each block is inputting and outputting thus came the creation of decimation and interpolation blocks where we specify the multiple factor. This is directly related to the sampling rate discussion we had in tutorial 2 where these special types of blocks are able to change the sampling rate to something else in order to comply with a specific piece of hardware such as our soundcard or a specific modulation/demodulation.<br />
<br />
* Synchronous (1:1) - Number of items at input port equals the number of items at output port. An example is the multiply_py_ff block. <br />
* Decimation (N:1) - Number of input items is a fixed multiple of the number of output items. Examples include filters such as decimating high pass and low pass filters (e.g., for sampling rate conversion).<br />
* Interpolation (1:M) - Number of output items is a fixed multiple of the number of input items. An example is the Interpolating FIR Filter.<br />
* General/Basic (N:M) - Provides no relation between the number of input items and the number of output items. An example is the rational resampler block which can be either a sync, decimator, or interpolator.<br />
<br />
When we insert data into our block, do we need more samples or less samples? Or put another way, should our sample rate change?<br />
<br />
What Type of Block Should we Use?<br />
* Sync Block or Basic Block<br />
<br />
<br />
=== 3.3.2. Adding Another Block to our OOT Module ===<br />
<br />
Now we know everything we need to know to create the block in gr_modtool. As a refresher, what would our gr_modtool command be?<br />
<br />
gr_modtool command...<br />
<pre><br />
gr-tutorial$ gr_modtool add -t sync -l python<br />
<br />
name: qpsk_demod_py_cb (c for complex input and b for byte/char/int8 output)<br /><br />
args: gray_code<br /><br />
QA Code: y<br />
</pre><br />
<br />
<br />
Now we have our files setup so we can begin with editing the qpsk_demod_py.py file in the /python folder<br />
<br />
=== 3.3.3. The QPSK Demodulator ===<br />
<br />
The code here is a little more than we have done before so let's take it in steps. First what do we expect our block to do? We can start with a constellation plot which shows the four different quadrants and their mappings to bit 0 and bit 1<br />
<br />
With [http://en.wikipedia.org/wiki/Phase-shift_keying#Quadrature_phase-shift_keying_.28QPSK.29 Gray coding] (adjacent only flips by 1). Note that the mapping we use here is different from the mapping on wikipedia:<br />
<br />
<br />
(-1+1j) 10 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 11 | 01 (1-1j)<br />
<br />
Without Gray coding:<br />
<br />
(-1+1j) 11 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 10 | 01 (1-1j)<br />
<br />
We can see that we will need to write an if-else statement to take into account the gray_code variable. We will also need four other if-else statements inside the main if-else so that we can pick the mapping. Our pseudocode will look as follows:<br />
<br />
if gray_code<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "11" = 3<br />
else<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "10" = 2<br />
<br />
So we have everything we need to implement. Let's go ahead and fill in our gr_modtoool placeholders. We can begin with def init. There are three changes. How do we use the variable gray_code outside the function (similar to what we did with multiple in the last example)? What are the input and output types in [http://docs.scipy.org/doc/numpy/user/basics.types.html? numpy]<br />
<br />
Changes to def ''init''...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
</syntaxhighlight><br />
<br />
Once we have our constructor setup, we can go onto the work function. For simplicity and beauty, let us call the pseudocode we made above a function "get_minimum_distance" that takes samples as input arguments. In our multiply_py_ff example, we took all the samples and multiplied them with with out[:] = in0*self.multiple. The in0 is actually a vector so contains many samples within it. The multiply example required the same operation for each sample so it was okay to simply operate on the entire vector but now we need to have different operations per sample so what do we do?<br />
<br />
How can we operate on samples in a vector?<br />
* loops!<br />
<br />
<syntaxhighlight lang="python"><br />
for i in range(0, len(output_items[0])):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
</syntaxhighlight><br />
<br />
<br />
Now we can move onto the get_minimum_distances(self, sample) function. We already have pseudo code so the next step is translating to Python. Below is a snip of what the code can look like. Again there are multiple ways to do this<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
</syntaxhighlight><br />
Let us try to fill in the other cases for gray code and non-gray code. Below is what the entire file Python file can look like once complete:<br />
<br />
qpsk_demod_py_cb.py<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class qpsk_demod_py(gr.sync_block):<br />
"""<br />
docstring for block qpsk_demod_py<br />
"""<br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
<br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 3 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
else:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 3 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 2 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
<br />
for i in range(0, len(in0)):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
<br />
<br />
Now that we have code, we know what's next!<br />
<br />
=== 3.3.4. Multiple QA Tests ===<br />
<br />
We can test our qpsk_demod_py for when it is in gray_code mode and when it's not in gray_code mode. To do that we need to setup multiple tests in our single QA file. QA tests generally follow the same setup from before. We select some inputs as tests and we check them against what we expect the outputs to be. The only difference from the multiply qa test is that this qa test requires more cases. There are four quadrants we need to map and two modes so in total there are eight test cases. We can open up our qa_qpsk_demod_py_ff.py file to change things.<br />
<br />
We can copy the def test_001_t from the qa_multiply_py_ff code which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def test_001_t (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (-6, 8, -11, 4, 6)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
This time we are working with a complex input so our src = blocks.vector_source_f must change. If we use the search bar in the manual we can find the other options:<br />
<br />
PIC of SEARCH<br />
<br />
b - bytes/unsigned char/int8<br /><br />
c - complex<br /><br />
f - float<br /><br />
i - int<br /><br />
s - short<br />
<br />
What do we change our source and sink vectors to?<br /><br />
src = blocks.vector_source_c (src_data)<br /><br />
dst = blocks.vector_sink_b ()<br /><br />
<br />
<br />
Before we move onto actual test cases, let us decide which mode we are testing for the test_001_t. We can create a new variable and assign it False (translates to 0) to test non-Gray code<br />
<br />
<syntaxhighlight lang="python"><br />
gray_code = False<br />
</syntaxhighlight><br />
Once we know we want to test non gray_code mappings, we can refer to our chart above and start placing in the proper inputs and outputs into the src_data and the expected_results. For instance if we were testing only two cases for non gray_code, we would do:<br />
<br />
<syntaxhighlight lang="python" line="line">src_data = ((-1-1j), (-1+1j))<br />
expected_result = (2, 3)<br />
</syntaxhighlight><br />
Last thing to do is call upon our new block in the "qpsk =" line and pass it the gray_code parameter<br />
<br />
qpsk = ?<br />
* qpsk = qpsk_demod_py_cb (gray_code)<br />
<br />
Now that we are done with the non gray_code test, we can simply create another test "def test_002_t (self):" and copy the contents underneath making sure that for this test we set gray_code = True and change the expected_result so it matches gray_code mapping. The full test is copied below:<br />
<br />
Full QA QPSK Demod Code...<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import numpy<br />
from qpsk_demod_py_cb import qpsk_demod_py<br />
<br />
class qa_qpsk_demod (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
gray_code = False<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (2, 3, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
def test_002_t (self):<br />
gray_code = True<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (3, 2, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qpsk_demod_py_cb.xml")<br />
</syntaxhighlight><br />
<br />
<br />
We can then run the test in Python and all should say something similar to:<br />
<br />
Ran 2 tests in 0.005s<br />
<br />
OK<br />
<br />
So once we verify it works as expected, we can then edit our XML file so that it is usable inside GRC.<br />
<br />
=== 3.3.5. XML Mods, Installation, and Running ===<br />
<br />
This XML is very similar to the XML file for the multiply_py_ff block so all we need to do is set the gray_code parameter and pick the correct input (complex) and output (byte) types. A copy of the full XML file is below:<br />
<br />
XML File for QPSK Demod<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>qpsk_demod_py</name><br />
<key>tutorial_qpsk_demod_py</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.qpsk_demod_py($gray_code)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<type>int</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<!-- e.g. int, float, complex, byte, short, xxx_vector, ...--><br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</block><br />
<br />
</syntaxhighlight><br />
<br />
<br />
We can then install as we did for the multiply block however we need to rerun cmake in order to take into account the new block:<br />
<pre><br />
cd build<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
</pre><br />
Then we can open up our GRC file from the beginning and place our missing block we just made.<br />
<br />
What is the Expected Output?<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_4.png<br />
<br />
== 3.4. Conclusion ==<br />
<br />
And that is it for now with Python. Let us know your thoughts before going on to the [[Guided_Tutorial_GNU_Radio_in_C++|C++ tutorial]].<br />
<br />
=== 3.4.1. Questions We Should Now be Able to Answer ===<br />
<br />
1. How do we set input- and output signatures in Python blocks?<br /><br />
2. Consider this I/O signature: (FIXME). Which input types and output types does it take?<br />
<br />
=== 3.4.2. Links to Further Information ===<br />
<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/BlocksCodingGuide Blocks Coding Guide]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModules Out-of-Tree Modules]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/TutorialsWritePythonApplications Writing Python Applications]<br />
<br />
== 3.5. Candidates for Future Sections ==<br />
<br />
Possible topics we may want to add depending on feedback and questions on the mailing list<br /><br />
- How to add documentation to blocks<br /><br />
- Constructor arguments, History, Output Multiple<br />
<br />
<br />
-----<br />
<br />
&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GNU_Radio_in_Python&diff=6416Guided Tutorial GNU Radio in Python2019-12-14T08:38:28Z<p>Cmrincon: update to v3.8.0.0</p>
<hr />
<div>&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
<br />
= Tutorial: Working with GNU Radio in Python =<br />
<br />
== Objectives ==<br />
<br />
* Python Blocks<br />
* OOT modules make the actual apps / functionality (GR is the API!)<br />
* How to add OOTs<br />
* How to add Python blocks with gr_modtool and how to code them<br />
* QPSK mapping<br />
* How to add GRC bindings for block<br />
<br />
== Prerequisites ==<br />
<br />
* Working Installation of GNU Radio 3.7.4 or later<br />
* [[Guided_Tutorial_GRC|GRC Tutorial]] (Recommended)<br />
* Familiar with Python<br />
<br />
-----<br />
<br />
== 3.1. Intro to Using GNU Radio with Python ==<br />
<br />
This tutorial goes through three parts. The first is how to modify, create, or simply understand the Python generated files GRC produces for us. The second is how to create our own custom out-of-tree (OOT) modules from the ground up. Lastly we go through an actual project to get more practice and build intuition on how we can use GNU Radio in our own project. As with the last tutorial, all the content - pictures, source code, and grc files - is included in the [https://github.com/gnuradio/gr-tutorial gr-tutorial repository] which we should have a local copy if we followed the directions from the [[Guided_Tutorial_GRC|GRC Tutorial]]<br />
<br />
Again we should have a directory with the solutions and a directory with our work as below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
/home/user/gnuradio/tutorials/solutions<br />
/home/user/gnuradio/tutorials/work<br />
</syntaxhighlight><br />
<br />
As a rule, if we hover over the GRC flowgraph images, we will be able to see the corresponding filename. Same applies for other images. Full code files are collapsed with the filename in the collapsed handle.<br />
<br />
=== 3.1.1. GRC Generated Python Files ===<br />
<br />
Let us look at a dial-tone example on the GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png<br />
<br />
When we click the '''Generate''' button, the terminal tells us it produced a .py file so let's open that to examine its code which is reproduced below:<br />
<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python3<br />
# -*- coding: utf-8 -*-<br />
<br />
#<br />
# SPDX-License-Identifier: GPL-3.0<br />
#<br />
# GNU Radio Python Flow Graph<br />
# Title: tutorial_three_1<br />
# GNU Radio version: 3.8.0.0<br />
<br />
from distutils.version import StrictVersion<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print("Warning: failed to XInitThreads()")<br />
<br />
from gnuradio import analog<br />
from gnuradio import audio<br />
from gnuradio import gr<br />
from gnuradio.filter import firdes<br />
import sys<br />
import signal<br />
from PyQt5 import Qt<br />
from argparse import ArgumentParser<br />
from gnuradio.eng_arg import eng_float, intx<br />
from gnuradio import eng_notation<br />
from gnuradio import qtgui<br />
<br />
class tutorial_three_1(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "tutorial_three_1")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("tutorial_three_1")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
<br />
try:<br />
if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
else:<br />
self.restoreGeometry(self.settings.value("geometry"))<br />
except:<br />
pass<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.samp_rate = samp_rate = 32000<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.audio_sink_0 = audio.sink(samp_rate, '', True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
<br />
<br />
<br />
def main(top_block_cls=tutorial_three_1, options=None):<br />
<br />
if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def sig_handler(sig=None, frame=None):<br />
Qt.QApplication.quit()<br />
<br />
signal.signal(signal.SIGINT, sig_handler)<br />
signal.signal(signal.SIGTERM, sig_handler)<br />
<br />
timer = Qt.QTimer()<br />
timer.start(500)<br />
timer.timeout.connect(lambda: None)<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.aboutToQuit.connect(quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
The first thing for us to realize is that the GRC can generate Python files that we can then modify to do things we wouldn't be able to do in GNU Radio Companion such as perform [[TutorialsSimulations|simulations]]. The libraries available in Python open up a whole new realm of possibilities! For now, we will explore the structure of the GRC Python files so we are comfortable creating more interesting applications.<br />
<br />
=== 3.1.2. Hello World Dissected ===<br />
<br />
While examining the code, we need to get familiar with documentation. GNU Radio uses Doxygen (the software) for the [http://gnuradio.org/doc/doxygen/ GNU Radio Manual]. The easiest way to go through the documentation is to go through the functions that we use so let us simplify our code by only including the bare bones needed to run the dial-tone example.<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env Python<br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, "")<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass<br />
<br />
</syntaxhighlight><br />
<br />
Let us examine this line by line:<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python<br />
</syntaxhighlight><br />
Tells the shell that this file is a Python file and to use the Python interpreter to run this file. Should always be included at the top to run from the terminal.<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr<br />
from gnuradio import audio<br />
from gnuradio import analog<br />
</syntaxhighlight><br />
<br />
Tells Python the modules to include. We must always have '''gr''' to run GNU Radio applications. The audio sink is included in the audio module and the signal_source_f is included in the analog module which is why we include them. [http://legacy.python.org/dev/peps/pep-0008/ PEP8] tells us we should import every module on its own line.<br />
<br />
<syntaxhighlight lang="python"><br />
class my_top_block(gr.top_block):<br />
</syntaxhighlight><br />
Define a class called "my_top_block" which is derived from another class, '''gr.top_block'''. This class is basically a container for the flow graph. By deriving from gr.top_block, we get all the hooks and functions we need to add blocks and interconnect them.<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self):<br />
</syntaxhighlight><br />
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.<br />
<br />
<syntaxhighlight lang="python"><br />
gr.top_block.__init__(self)<br />
</syntaxhighlight><br />
The parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br />
<br />
<syntaxhighlight lang="python"><br />
sample_rate = 32000<br />
ampl = 0.1<br />
</syntaxhighlight><br />
Variable declarations for sampling rate and amplitude that we will later use.<br />
<br />
=== 3.1.3. A Look at Documentation ===<br />
<br />
<syntaxhighlight lang="python"><br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
</syntaxhighlight><br />
Here we are using functions from GNU Radio so let's have a look at the documentation for '''analog.sig_source_f''' which is available in [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html the GNU Radio manual]. We can find it easily by using the search function as below:<br />
<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_search.png<br />
<br />
We can then scroll down to '''Member Function Documentation''' to see how the function is used and the parameters it accepts as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_source.png<br />
<br />
We can see that our function '''analog.sig_source_f''' takes in 5 parameters but in our code we are only using 4. There is no error because the last input '''offset''' is set to "0" by default as shown in the documentation. The first input is the '''sampling_freq''' which we defined as '''sample_rate''' in our code. The second input is asking for a '''gr::analog::gr_waveform_t''' waveform so let's click that [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177 link] to find out more.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_waveform.png<br />
<br />
We can see that there are a couple of options to choose from. In this case we chose '''analog.GR_SIN_WAVE'''. The third input is '''wave_freq''' which we input "350" or "440". The fourth input is '''ampl''' which we defined as '''ampl'''.<br />
<br />
<syntaxhighlight lang="python"><br />
dst = audio.sink(sample_rate, "")<br />
</syntaxhighlight><br />
Because documentation is so important, let's look at another example. Again, we can look at the documentation for '''audio.sink''' which is available on the GNU Radio Manual through the search function:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink.png<br />
<br />
We can then as before scroll down to the '''Member Function Documentation''' to see the parameters it accepts:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/hello_sink_detail.png<br />
<br />
This time we have 3 inputs with the last being optional. In our code, for the first input '''sampling_rate''' we used are '''sample_rate''' variable. In the second input, we have a choice for what device to use for audio output. If we leave it alone as "" then it'll choose the default on our machine.<br />
<br />
=== 3.1.4. Connecting the Block Together ===<br />
<br />
<syntaxhighlight lang="python"><br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
</syntaxhighlight><br />
The general syntax for connecting blocks is self.connect(block1, block2, block3, ...) which would connect the output of block1 with the input of block2, the output of block2 with the input of block3 and so on. We can connect as many blocks as we wish with one connect() call. However this only work when there is a one-to-one correspondence. If we go back to our initial flowgraph, there are 2 inputs to the '''Audio Sink''' block. The way to connect them is by using the syntax above. The first line connects the only output of src0 (350 Hz waveform) to the first input of dst (Audio Sink). The second line connects the only output of src1 (440 Hz waveform) to the second input of dst (Audio Sink). The code so far is equivalent to the flowgraph we have created in the beginning; the rest of the lines simply start the flowgraph and provide a keyboard interrupt.<br />
<br />
<syntaxhighlight lang="python"><br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except KeyboardInterrupt:<br />
pass<br />
</syntaxhighlight><br />
<br />
Luckily we are past the early years of GNU Radio when there was no GRC to make the Python files for us. Nowadays we can simply click things together in GRC instead of having to write code in Python to build flowgraphs. Still, a good understanding of what is going on every time we run GRC is good to know as it gives us more control of what we want the program to do.<br />
<br />
=== 3.1.5. Modifying the GRC Generated Python File ===<br />
<br />
For instance, what if we wanted to change variables such as frequency and amplitude when a certain event occurs. How do we implement if statements, state machines, etc in GNU Radio? One way is to create our own blocks which we will delve into at length later. Another is to modify our GRC generated python file.<br />
<br />
Our friend heard we were into RF so being cheap he has asked us to power his house using RF. He wants us to give him high power during the day so he can watch TV and play video games while at night give him low power so he power his alarm clock to wake up for work in the morning. We first need to setup the clock which keeps track of the day and gives us 1 for day or 0 for night. Once we get the time we can send him power through a sine wave using our massive terawatt amplifier and massive dish in our backyard. We did the calculations and we want to pulse at 1kHz, 1 amplitude during the day and 100Hz, 0.3 amplitude at night. Here's what we came up with in GRC:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else.png<br />
<br />
<span style="color:blue">- '''Frequency''' to "freq", '''Amplitude''' to "ampl"</span><br /><br />
<span style="color:red">- '''ID''' to "probe"</span><br /><br />
- Everything else is visible<br />
<br />
The top section keeps track of time and will switch from 0 to 1 while the bottom section sends the pulse. The problem we encounter however is that there is no if-statement block. Sure we can tie the probe to the frequency as we did in tutorial2 for the singing sine wave but that only allows changing by a factor. What if we wanted to change multiple things and not by a linear factor? Let's start by running the flowgraph to make sure we get the output as below:<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/if_else_output.png<br />
<br />
Now we can open up the GRC generated python file if_else.py which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
#!/usr/bin/env python2<br />
# -*- coding: utf-8 -*-<br />
##################################################<br />
# GNU Radio Python Flow Graph<br />
# Title: If Else<br />
# Generated: Thu Sep 13 11:39:57 2018<br />
##################################################<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print "Warning: failed to XInitThreads()"<br />
<br />
from PyQt4 import Qt<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import eng_notation<br />
from gnuradio import gr<br />
from gnuradio import qtgui<br />
from gnuradio.eng_option import eng_option<br />
from gnuradio.filter import firdes<br />
from optparse import OptionParser<br />
import sip<br />
import sys<br />
import threading<br />
import time<br />
from gnuradio import qtgui<br />
<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "If Else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("If Else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel("freq"+": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel("ampl"+": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
'QT GUI Plot', #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(-1, True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
if not True:<br />
self.qtgui_time_sink_x_0.disable_legend()<br />
<br />
labels = ['', '', '', '', '',<br />
'', '', '', '', '']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ["blue", "red", "green", "black", "cyan",<br />
"magenta", "yellow", "dark red", "dark green", "blue"]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
<br />
for i in xrange(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
<br />
<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_1.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_1.set_amplitude(self.ampl)<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
from distutils.version import StrictVersion<br />
if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We are only concerned about a couple of parts namely the part where the probe is being read:<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
We can see that the variable '''val''' is obtaining the value of the probe block. We can write our if-else statement here based on the value of '''val''' to change the amplitude and frequency of our sine wave. But how do we change the frequency and amplitude? We can use the part where the '''QT GUI Entry''' updates the flowgraph. For the variable freq:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_1.set_frequency(self.freq)<br />
</syntaxhighlight><br />
<br />
and for the variable ampl:<br />
<br />
<syntaxhighlight lang="python"><br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_1.set_amplitude(self.ampl)<br />
</syntaxhighlight><br />
We can see that the functions set_ampl and set_freq can be used for just that - setting the amplitude and the frequency. Thus we can go back and modify our probe function with the if-else statement to give power to our friend.<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
</syntaxhighlight><br />
<br />
Now there is one more thing we need to take care of. GRC has compiled the python file in the order of creation of the elements, which was okay as long as there were no crossreferences. With the introduced adaptation (calling set_ampl and set_freq inside the _variable_function_probe_0_probe()) we need to fix the order of declarations. As set_ampl and set_freq both modify parameters of analog_sig_source_x_1 but analog_sig_source_x_1 is not instantiated before line 150, we have to move the declaration of the _variable_function_probe_0_probe() and everything related below that.<br />
<br />
<syntaxhighlight lang="python"><br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
</syntaxhighlight><br />
<br />
Full code copied below:<br />
<br />
<br />
<syntaxhighlight lang="python"><br />
#!/usr/bin/env python2<br />
# -*- coding: utf-8 -*-<br />
##################################################<br />
# GNU Radio Python Flow Graph<br />
# Title: If Else<br />
# Generated: Thu Sep 13 11:39:57 2018<br />
##################################################<br />
<br />
if __name__ == '__main__':<br />
import ctypes<br />
import sys<br />
if sys.platform.startswith('linux'):<br />
try:<br />
x11 = ctypes.cdll.LoadLibrary('libX11.so')<br />
x11.XInitThreads()<br />
except:<br />
print "Warning: failed to XInitThreads()"<br />
<br />
from PyQt4 import Qt<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio import eng_notation<br />
from gnuradio import gr<br />
from gnuradio import qtgui<br />
from gnuradio.eng_option import eng_option<br />
from gnuradio.filter import firdes<br />
from optparse import OptionParser<br />
import sip<br />
import sys<br />
import threading<br />
import time<br />
from gnuradio import qtgui<br />
<br />
<br />
class if_else(gr.top_block, Qt.QWidget):<br />
<br />
def __init__(self):<br />
gr.top_block.__init__(self, "If Else")<br />
Qt.QWidget.__init__(self)<br />
self.setWindowTitle("If Else")<br />
qtgui.util.check_set_qss()<br />
try:<br />
self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))<br />
except:<br />
pass<br />
self.top_scroll_layout = Qt.QVBoxLayout()<br />
self.setLayout(self.top_scroll_layout)<br />
self.top_scroll = Qt.QScrollArea()<br />
self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)<br />
self.top_scroll_layout.addWidget(self.top_scroll)<br />
self.top_scroll.setWidgetResizable(True)<br />
self.top_widget = Qt.QWidget()<br />
self.top_scroll.setWidget(self.top_widget)<br />
self.top_layout = Qt.QVBoxLayout(self.top_widget)<br />
self.top_grid_layout = Qt.QGridLayout()<br />
self.top_layout.addLayout(self.top_grid_layout)<br />
<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.restoreGeometry(self.settings.value("geometry").toByteArray())<br />
<br />
<br />
##################################################<br />
# Variables<br />
##################################################<br />
self.variable_function_probe_0 = variable_function_probe_0 = 0<br />
self.samp_rate = samp_rate = 32000<br />
self.freq = freq = 1000<br />
self.ampl = ampl = 1<br />
<br />
##################################################<br />
# Blocks<br />
##################################################<br />
self.probe = blocks.probe_signal_f()<br />
self._freq_tool_bar = Qt.QToolBar(self)<br />
self._freq_tool_bar.addWidget(Qt.QLabel("freq"+": "))<br />
self._freq_line_edit = Qt.QLineEdit(str(self.freq))<br />
self._freq_tool_bar.addWidget(self._freq_line_edit)<br />
self._freq_line_edit.returnPressed.connect(<br />
lambda: self.set_freq(int(str(self._freq_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._freq_tool_bar)<br />
self._ampl_tool_bar = Qt.QToolBar(self)<br />
self._ampl_tool_bar.addWidget(Qt.QLabel("ampl"+": "))<br />
self._ampl_line_edit = Qt.QLineEdit(str(self.ampl))<br />
self._ampl_tool_bar.addWidget(self._ampl_line_edit)<br />
self._ampl_line_edit.returnPressed.connect(<br />
lambda: self.set_ampl(int(str(self._ampl_line_edit.text().toAscii()))))<br />
self.top_grid_layout.addWidget(self._ampl_tool_bar)<br />
<br />
self.qtgui_time_sink_x_0 = qtgui.time_sink_f(<br />
1024, #size<br />
samp_rate, #samp_rate<br />
'QT GUI Plot', #name<br />
1 #number of inputs<br />
)<br />
self.qtgui_time_sink_x_0.set_update_time(0.10)<br />
self.qtgui_time_sink_x_0.set_y_axis(-1, 1)<br />
<br />
self.qtgui_time_sink_x_0.set_y_label('Amplitude', "")<br />
<br />
self.qtgui_time_sink_x_0.enable_tags(-1, True)<br />
self.qtgui_time_sink_x_0.set_trigger_mode(qtgui.TRIG_MODE_FREE, qtgui.TRIG_SLOPE_POS, 0.0, 0, 0, "")<br />
self.qtgui_time_sink_x_0.enable_autoscale(False)<br />
self.qtgui_time_sink_x_0.enable_grid(False)<br />
self.qtgui_time_sink_x_0.enable_axis_labels(True)<br />
self.qtgui_time_sink_x_0.enable_control_panel(False)<br />
self.qtgui_time_sink_x_0.enable_stem_plot(False)<br />
<br />
if not True:<br />
self.qtgui_time_sink_x_0.disable_legend()<br />
<br />
labels = ['', '', '', '', '',<br />
'', '', '', '', '']<br />
widths = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
colors = ["blue", "red", "green", "black", "cyan",<br />
"magenta", "yellow", "dark red", "dark green", "blue"]<br />
styles = [1, 1, 1, 1, 1,<br />
1, 1, 1, 1, 1]<br />
markers = [-1, -1, -1, -1, -1,<br />
-1, -1, -1, -1, -1]<br />
alphas = [1.0, 1.0, 1.0, 1.0, 1.0,<br />
1.0, 1.0, 1.0, 1.0, 1.0]<br />
<br />
for i in xrange(1):<br />
if len(labels[i]) == 0:<br />
self.qtgui_time_sink_x_0.set_line_label(i, "Data {0}".format(i))<br />
else:<br />
self.qtgui_time_sink_x_0.set_line_label(i, labels[i])<br />
self.qtgui_time_sink_x_0.set_line_width(i, widths[i])<br />
self.qtgui_time_sink_x_0.set_line_color(i, colors[i])<br />
self.qtgui_time_sink_x_0.set_line_style(i, styles[i])<br />
self.qtgui_time_sink_x_0.set_line_marker(i, markers[i])<br />
self.qtgui_time_sink_x_0.set_line_alpha(i, alphas[i])<br />
<br />
self._qtgui_time_sink_x_0_win = sip.wrapinstance(self.qtgui_time_sink_x_0.pyqwidget(), Qt.QWidget)<br />
self.top_grid_layout.addWidget(self._qtgui_time_sink_x_0_win)<br />
self.blocks_throttle_0 = blocks.throttle(gr.sizeof_float*1, samp_rate,True)<br />
self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_SIN_WAVE, freq, ampl, 0)<br />
self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_SQR_WAVE, 0.1, 1, 0)<br />
<br />
def _variable_function_probe_0_probe():<br />
while True:<br />
val = self.probe.level()<br />
print val<br />
if val == 1:<br />
self.set_ampl(1)<br />
self.set_freq(1000)<br />
else:<br />
self.set_ampl(.3)<br />
self.set_freq(100)<br />
try:<br />
self.set_variable_function_probe_0(val)<br />
except AttributeError:<br />
pass<br />
time.sleep(1.0 / (10))<br />
_variable_function_probe_0_thread = threading.Thread(target=_variable_function_probe_0_probe)<br />
_variable_function_probe_0_thread.daemon = True<br />
_variable_function_probe_0_thread.start()<br />
<br />
##################################################<br />
# Connections<br />
##################################################<br />
self.connect((self.analog_sig_source_x_0, 0), (self.blocks_throttle_0, 0))<br />
self.connect((self.analog_sig_source_x_1, 0), (self.qtgui_time_sink_x_0, 0))<br />
self.connect((self.blocks_throttle_0, 0), (self.probe, 0))<br />
<br />
def closeEvent(self, event):<br />
self.settings = Qt.QSettings("GNU Radio", "if_else")<br />
self.settings.setValue("geometry", self.saveGeometry())<br />
event.accept()<br />
<br />
def get_variable_function_probe_0(self):<br />
return self.variable_function_probe_0<br />
<br />
def set_variable_function_probe_0(self, variable_function_probe_0):<br />
self.variable_function_probe_0 = variable_function_probe_0<br />
<br />
def get_samp_rate(self):<br />
return self.samp_rate<br />
<br />
def set_samp_rate(self, samp_rate):<br />
self.samp_rate = samp_rate<br />
self.qtgui_time_sink_x_0.set_samp_rate(self.samp_rate)<br />
self.blocks_throttle_0.set_sample_rate(self.samp_rate)<br />
self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)<br />
self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)<br />
<br />
def get_freq(self):<br />
return self.freq<br />
<br />
def set_freq(self, freq):<br />
self.freq = freq<br />
Qt.QMetaObject.invokeMethod(self._freq_line_edit, "setText", Qt.Q_ARG("QString", str(self.freq)))<br />
self.analog_sig_source_x_1.set_frequency(self.freq)<br />
<br />
def get_ampl(self):<br />
return self.ampl<br />
<br />
def set_ampl(self, ampl):<br />
self.ampl = ampl<br />
Qt.QMetaObject.invokeMethod(self._ampl_line_edit, "setText", Qt.Q_ARG("QString", str(self.ampl)))<br />
self.analog_sig_source_x_1.set_amplitude(self.ampl)<br />
<br />
<br />
def main(top_block_cls=if_else, options=None):<br />
<br />
from distutils.version import StrictVersion<br />
if StrictVersion(Qt.qVersion()) >= StrictVersion("4.5.0"):<br />
style = gr.prefs().get_string('qtgui', 'style', 'raster')<br />
Qt.QApplication.setGraphicsSystem(style)<br />
qapp = Qt.QApplication(sys.argv)<br />
<br />
tb = top_block_cls()<br />
tb.start()<br />
tb.show()<br />
<br />
def quitting():<br />
tb.stop()<br />
tb.wait()<br />
qapp.connect(qapp, Qt.SIGNAL("aboutToQuit()"), quitting)<br />
qapp.exec_()<br />
<br />
<br />
if __name__ == '__main__':<br />
main()<br />
<br />
</syntaxhighlight><br />
<br />
We can then simply run our flowgraph from outside of GRC using<br />
<br />
<pre><br />
$ python if_else_mod.py<br />
</pre><br />
We should be able to see the numbers 0 and 1 on the terminal and the sine wave changing amplitude and frequency as the numbers change.<br />
<br />
This tutorial is merely an introduction on using python in GNU Radio, for a more advanced tutorial see [[TutorialsWritePythonApplications]] .<br />
<br />
== 3.2. Where Do Blocks Come From? ==<br />
<br />
Now that we have covered some of the ways we can modify our GRC generated Python files, we can see how to make our own blocks to add functionality to suit our specific project needs. Let us start off simple to get a feel for the waters. We will be making a block that is able to multiply a signal by the number we specify. The first thing we need to realize is that GNU Radio comes with gr_modtool, a utility that makes creating OOT Modules easy. Let us open up a terminal and begin in a project folder for the tutorial three.<br />
<br />
=== 3.2.1. Using gr_modtool ===<br />
<br />
Before we begin, we need to figure out what the commands for gr_modtool are so let's ask for help.<br />
<br />
<pre>$ gr_modtool help<br />
Usage:<br />
gr_modtool [options] -- Run with the given options.<br />
gr_modtool help -- Show a list of commands.<br />
gr_modtool help -- Shows the help for a given command.<br />
<br />
List of possible commands:<br />
<br />
Name Aliases Description<br />
=====================================================================<br />
disable dis Disable block (comments out CMake entries for files)<br />
info getinfo,inf Return information about a given module<br />
remove rm,del Remove block (delete files and remove Makefile entries)<br />
makexml mx Make XML file for GRC block bindings<br />
add insert Add block to the out-of-tree module.<br />
newmod nm,create Create a new out-of-tree module<br />
</pre><br />
We immediately see there are many commands available. In this tutorial we will only cover '''newmod''' and '''add'''; however, the thorough explanation should enable smooth usage of the other gr_modtool commands without guidance.<br />
<br />
First, we notice that in addition to getting help seeing the commands we can use, we can also request more information on individual commands. Let us start with newmod as that is the command to create a new out-of-tree module.<br />
<br />
<pre><br />
$ gr_modtool help newmod<br />
Usage: gr_modtool nm [options].<br />
Call gr_modtool without any options to run it interactively.<br />
<br />
Options:<br />
General options:<br />
-h, --help Displays this help message.<br />
-d DIRECTORY, --directory=DIRECTORY<br />
Base directory of the module. Defaults to the cwd.<br />
-n MODULE_NAME, --module-name=MODULE_NAME<br />
Use this to override the current module's name (is<br />
normally autodetected).<br />
-N BLOCK_NAME, --block-name=BLOCK_NAME<br />
Name of the block, where applicable.<br />
--skip-lib Don't do anything in the lib/ subdirectory.<br />
--skip-swig Don't do anything in the swig/ subdirectory.<br />
--skip-Python Don't do anything in the Python/ subdirectory.<br />
--skip-grc Don't do anything in the grc/ subdirectory.<br />
--scm-mode=SCM_MODE<br />
Use source control management (yes, no or auto).<br />
-y, --yes Answer all questions with 'yes'. This can overwrite<br />
and delete your files, so be careful.<br />
<br />
New out-of-tree module options:<br />
--srcdir=SRCDIR Source directory for the module template<br />
</pre><br />
Now that we have read over the list of commands for newmod, we can deduce that the one we want to pick is -n which is the default so we can simply type the MODULE_NAME after newmod. It is actually advised to avoid using "-n" as for other commands it can override the auto-detected name. For now, let's ignore the other options.<br />
<br />
=== 3.2.2. Setting up a new block ===<br />
<br />
<pre><br />
$ gr_modtool newmod tutorial<br />
Creating out-of-tree module in ./gr-tutorial... Done.<br />
Use 'gr_modtool add' to add a new block to this currently empty module.<br />
</pre><br />
We should now see a new folder, gr-tutorial, in our current directory. Let's examine the folder to figure out what gr_modtool has done for us.<br />
<br />
<pre><br />
gr-tutorial$ ls<br />
apps cmake CMakeLists.txt docs examples grc include lib Python swig<br />
</pre><br />
Since we are dealing with Python in this tutorial we only need to concern ourselves with the Python folder and the grc folder. Before we can dive into code, we need to create a block from a template. There are actually four different types of Python blocks, however it's a little too soon to discuss that. We will dive into the synchronous 1:1 input to output block to make explaining things easier (this is a block that outputs as many items as it gets as input, but don't worry about this right now).<br />
<br />
Now we know the language we want to write our block in (Python) and the type of block (synchronous block) we can now add a block to our module. Again, we should run the gr_modtool help command until we are familiar with the different commands. We see that the '''add''' command is what we are looking for. Now we need to run help on the add command in order to see what we need to enter given the information we have so far.<br />
<br />
<pre><br />
gr-tutorial$ gr_modtool help add<br />
... (General Options from Last Help)<br />
Add module options:<br />
-t BLOCK_TYPE, --block-type=BLOCK_TYPE<br />
One of sink, source, sync, decimator, interpolator,<br />
general, tagged_stream, hier, noblock.<br />
--license-file=LICENSE_FILE<br />
File containing the license header for every source<br />
code file.<br />
--argument-list=ARGUMENT_LIST<br />
The argument list for the constructor and make<br />
functions.<br />
--add-Python-qa If given, Python QA code is automatically added if<br />
possible.<br />
--add-cpp-qa If given, C++ QA code is automatically added if<br />
possible.<br />
--skip-cmakefiles If given, only source files are written, but<br />
CMakeLists.txt files are left unchanged.<br />
-l LANG, --lang=LANG<br />
Language (cpp or Python)<br />
</pre><br />
We can see the '''-l LANG''' and the '''-t BLOCK_TYPE''' are relevant for our example. Thus when creating our new block, we know the command. When prompted for a name simply enter "multiply_py_ff", when prompted for an argument list enter "multiple", and when prompted for Python QA (Quality Assurance) code type "y", or just hit enter (the capital letter is the default value).<br />
<br />
<pre>gr-tutorial$ gr_modtool add -t sync -l python<br />
GNU Radio module name identified: tutorial<br />
Language: Python<br />
Enter name of block/code (without module name prefix): multiply_py_ff<br />
Block/code identifier: multiply_py_ff<br />
Enter valid argument list, including default arguments: multiple<br />
Add Python QA code? [Y/n] y<br />
Adding file 'Python/multiply_py_ff.py'...<br />
Adding file 'Python/qa_multiply_py_ff.py'...<br />
Editing Python/CMakeLists.txt...<br />
Adding file 'grc/tutorial_multiply_py_ff.xml'...<br />
Editing grc/CMakeLists.txt...<br />
</pre><br />
We notice 5 changes: Two changes to CMakeLists.txt files, one new file <code>qa_multiply_py_ff.py</code> which is used to test our code, one new file <code>multiply_py_ff.py</code> which is the functional part, and one new file <code>tutorial_multiply_py_ff.xml</code>, which is used to link the block to the GRC. Again all this happens in the Python and grc subfolders.<br />
<br />
==== 3.2.2.1. What's with the <code>_ff</code>? ====<br />
<br />
For blocks with strict types, we use suffixes to declare the input and output types. This block operates on floats, so we give it the suffix <code>_ff</code>: Float in, float out. Other suffixes are <code>_cc</code> (complex in, complex out), or simply <code>_f</code> (a sink or source with no in- or outputs that uses floats). For a more detailed description, see the [[FAQ]] or the [[BlocksCodingGuide]].<br />
<br />
=== 3.2.3. Modifying the Python Block File ===<br />
<br />
Let's begin with the multiply_py_ff.py file found in the Python folder. Opening it without any changes gives the following:<br />
<br />
<syntaxhighlight lang="python"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class multiply_py_ff(gr.sync_block):<br />
"""<br />
docstring for block multiply_py_ff<br />
"""<br />
def __init__(self, multiple):<br />
gr.sync_block.__init__(self,<br />
name="multiply_py_ff",<br />
in_sig=[<+numpy.float+>],<br />
out_sig=[<+numpy.float+>])<br />
self.multiple = multiple<br />
<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
Let's take this one line by line as our first Python examples. We are already familiar with the imports so we will skip those lines. We are familiar with the constructor (init) of Python so can immediately see that if we want to use our variable "multiple", we need to add another line. Let us not forget to preserve those spaces as some code editors like to add tabs to new lines. How do we use the variable multiple?<br />
<br />
How to use variable multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, multiple):<br />
self.multiple = multiple<br />
gr.sync_block.__init__(self,<br />
</syntaxhighlight><br />
<br />
<br />
We notice that there are "&lt;''...''&gt;" scattered in many places. These placeholders are from gr_modtool and tell us where we need to alter things<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[<+numpy.float+>]<br />
out_sig=[<+numpy.float+>]<br />
</syntaxhighlight><br />
The '''gr.sync_block.''init''''' takes in 4 inputs: self, name, and the size/type of the input and output vectors. First, we want to make the item size a single precision float or numpy.float32 by removing the "&lt;''" and the "''&gt;". If we wanted vectors, we could define those as in_sig=[(numpy.float32,4),numpy.float32]. This means there are two input ports, one for vectors of 4 floats and the other for scalars. It is worth noting that if in_sig contains nothing then it becomes a source block, and if out_sig contains nothing it becomes a sink block (provided we change return <code>len(output_items[0])</code> to return <code>len(input_items[0])</code> since output_items is empty). Our changes to the first placeholders should appear as follows:<br />
<br />
<syntaxhighlight lang="python"><br />
in_sig=[numpy.float32]<br />
out_sig=[numpy.float32]<br />
</syntaxhighlight><br />
The other piece of code that has the placeholders is in the work function but let us first get a better understanding of the work function:<br />
<br />
<syntaxhighlight lang="python"><br />
def work(self, input_items, output_items)<br />
</syntaxhighlight><br />
The work function is where the actual processing happens, where we want our code to be. Because this is a sync block, the number of input items always equals the number of output items because synchronous block ensures a fixed output to input rate. There are also decimation and interpolation blocks where the number of output items are a user specified multiple of the number of input items. We will further discuss when to use what block type in the third section of this tutorial. For now we look at the placeholder:<br />
<br />
<syntaxhighlight lang="python"><br />
in0 = input_items[0]<br />
out = output_items[0]<br />
# <+signal processing here+><br />
out[:] = in0<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
The "in0" and "out" simply store the input and output in a variable to make the block easier to write. The signal processing can be anything including if statements, loops, function calls but for this example we only need to modify the out[:] = in0 line so that our input signal is multiplied by our variable multiple. What do we need to add to make the in0 multiply by our multiple?<br />
<br />
How to Multiple...<br />
<br />
<syntaxhighlight lang="python"><br />
out[:] = in0*self.multiple<br />
</syntaxhighlight><br />
<br />
That's it! Our block should now be able to multiply but to be sure we have these things called Quality Assurance tests!<br />
<br />
=== 3.2.4. QA Tests ===<br />
<br />
Now we need to test it to make sure it will run correctly when we install it unto GNU Radio. This is a very important step and we should never forget to include tests with our code! Let us open up qa_multiply_py_ff.py which is copied below:<br />
<br />
<syntaxhighlight lang="python"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
from multiply_py_ff import multiply_py_ff<br />
<br />
class qa_multiply_py_ff (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
# set up fg<br />
self.tb.run ()<br />
# check data<br />
<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_multiply_py_ff, "qa_multiply_py_ff.xml")<br />
</syntaxhighlight><br />
gr_unittest adds support for checking approximate equality of tuples of float and complex numbers. The only part we need to worry about is the def test_001_t function. We know we need input data so let us create data. We want it to be in the form of a vector so that we can test multiple values at once. Let us create a vector of floats<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
</syntaxhighlight><br />
We also need output data so we can compare the input of the block to ensure that it is doing what we expect it to do. Let us multiply by 2 for simplicity.<br />
<br />
<syntaxhighlight lang="python">expected_result = (0, 2, -4, 11, -1)<br />
</syntaxhighlight><br />
Now we can create a flowgraph as we have when we first introduced using Python in GNU Radio. We can use the blocks library specifically the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__source__f.html vector_source_f] function and the [http://gnuradio.org/doc/doxygen/classgr_1_1blocks_1_1vector__sink__f.html vector_sink_f] function which are linked to the doxygen manual which we should be able to read and understand. Let us assign three variables "src", "mult", and "snk" to the blocks. The first is shown below:<br />
<br />
<syntaxhighlight lang="python">src = blocks.vector_source_f(src_data)</syntaxhighlight><br />
The rest are hidden below as an exercise:<br />
<br />
What do we assign snk and mult?<br />
<br />
<syntaxhighlight lang="python">mult = multiply_py_ff(2)<br />
snk = blocks.vector_sink_f()<br />
</syntaxhighlight><br />
<br />
<br />
Now we need to connect everything as src <s>&gt; mult</s>&gt; snk. Instead of using self.connect as we did in our other blocks we need to use self.tb.connect because of the setUp function. Below is how we would connect the src block to the mult block.<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (src, mult)<br />
</syntaxhighlight><br />
<br />
How would we connect the other blocks together?<br />
<br />
<syntaxhighlight lang="python"><br />
self.tb.connect (mult, snk)<br />
</syntaxhighlight><br />
<br />
<br />
Then we can run the graph and store the data from the sink as below:<br />
<br />
<syntaxhighlight lang="python">self.tb.run ()<br />
result_data = snk.data ()<br />
</syntaxhighlight><br />
Lastly we can run our comparison function that will tell us whether the numbers match up to 6 decimal places. We are using the assertFloatTuplesAlmostEqual instead of the "regular assert functions"https://docs.python.org/2/library/unittest.html#assert-methods included in python's unittest because there may be situations where we cannot get a=b due to rounding in floating point numbers.<br />
<br />
<syntaxhighlight lang="python"><br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
All together the new test_001_t function should appear as below:<br />
<br />
<syntaxhighlight lang="python"><br />
src_data = (0, 1, -2, 5.5, -0.5)<br />
expected_result = (0, 2, -4, 11, -1)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
snk = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, snk)<br />
self.tb.run ()<br />
result_data = snk.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
<br />
We can then go to the python directory and run:<br />
<br />
<pre><br />
gr-tutorial/python$ python qa_multiply_py_ff.py<br />
.<br />
----------------------------------------------------------------------<br />
Ran 1 test in 0.004s<br />
<br />
OK<br />
</pre><br />
<br />
While we are here, we should take a break to change one of the numbers in the src_data to ensure that the block is actually checking the values and to simply see what an error looks like. Python allows for really quick testing of blocks without having to compile anything; simply change something and re-run the QA test.<br />
<br />
=== 3.2.5. XML Files ===<br />
<br />
At this point we should have written a Python block and a QA test for that block. The next thing to do is edit the XML file in the grc folder so that we can get another step closer to using it in GRC. GRC uses the XML files for all the options we see. We actually don't need to write any Python or C++ code to have a block display in GRC but of course if we would connect it, it wouldn't do anything but give errors. We should get out of the python folder and go to the grc folder where all the XML files reside. There is a tool in gr_modtool called makexml but it is only available for C++ blocks. Let us open the tutorial_multiply_py_ff.xml file copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>multiply_py_ff</name><br />
<key>tutorial_multiply_py_ff</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.multiply_py_ff($multiple)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>...</name><br />
<key>...</key><br />
<type>...</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<source><br />
<name>out</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</source><br />
</block></syntaxhighlight><br />
<br />
We can change the name that appears and the category it will appear in GRC. The category is where the block will be found in GRC. Examples of categories tag are '''Audio''' and '''Waveform Generators''' used in previous examples. Examples of names tag are the '''QT GUI Time Sink''' or the '''Audio Sink'''. Again, we can go through the file and find the modtool place holders. The first is copied below:<br />
<br />
<syntaxhighlight lang="XML" line="line"><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
</syntaxhighlight><br />
This is referring to the parameter that we used in the very beginning when creating our block: the variable called "multiple". We can fill it in as below:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<param><br />
<name>Multiple</name><br />
<key>multiple</key><br />
<type>float</type><br />
</param><br />
</syntaxhighlight><br />
The next placeholder can be found in the sink and source tags:<br />
<br />
<syntaxhighlight lang="XML" line="line"> <br />
<sink><br />
<name>in</name><br />
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type><br />
</sink><br />
</syntaxhighlight><br />
We can see that it is asking for a type so we can simply erase everything in the tag and replace it with "float" for both the source and the sink blocks. That should do it for this block. The best way to get more experience writing xml files is to look at the source code of previously made blocks such as the existing multiple block. Let's go back to this and use the documentation tag!<br />
<br />
=== 3.2.6. Installing Python Blocks ===<br />
<br />
Now that we have edited the XML file; we are ready to install the block into the GRC. First, we need to get out of the /grc directory and create a build directory called "build". Inside the build directory, we can then run a series of commands:<br />
<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
<br />
We should then open up the GRC and take a look at the new block.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_blockname.png<br />
<br />
We can see the category and the block name. When we drag it into the GRC workspace, we can see the multiple variable we set in the param tag of the XML file.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/xml_param.png<br />
<br />
Now that we can see our block, let us test it to make sure it works. Below is an example of one of the many ways to check whether it is actually multiplying.<br />
<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_2.png<br />
<br />
== 3.3. My QPSK Demodulator for Satellites ==<br />
<br />
Now that we have a better understanding on how to add new blocks to GNU Radio for use in the GRC, we can do another example. This time we will be creating a QPSK demodulator block using the same process as before in the same module. Let's first setup the scenario. There is a "noise source" that outputs data in complex float format but let's pretend it comes from a satellite being aimed at our computer. Our secret agent insider tells us this particular satellite encodes digital data using QPSK modulation. We can decode this using a QPSK demodulator that outputs data into bytes. Our insider tells us the space manual doesn't specify whether it's gray code or not. We want to read the bytes using a time sink. What would our flowgraph look like?<br />
<br />
Incomplete Flowgraph...<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_3.png<br />
<br />
<br />
Now that we know the input type, output type, and parameters, we can ask the question we skipped with our multiply_py_ff block. What type of block do we want?<br />
<br />
=== 3.3.1. Choosing a Block Type ===<br />
<br />
The GNU Radio scheduler optimizes for throughput (as opposed to latency). To do this it needs to know the number of samples each block is inputting and outputting thus came the creation of decimation and interpolation blocks where we specify the multiple factor. This is directly related to the sampling rate discussion we had in tutorial 2 where these special types of blocks are able to change the sampling rate to something else in order to comply with a specific piece of hardware such as our soundcard or a specific modulation/demodulation.<br />
<br />
* Synchronous (1:1) - Number of items at input port equals the number of items at output port. An example is the multiply_py_ff block. <br />
* Decimation (N:1) - Number of input items is a fixed multiple of the number of output items. Examples include filters such as decimating high pass and low pass filters (e.g., for sampling rate conversion).<br />
* Interpolation (1:M) - Number of output items is a fixed multiple of the number of input items. An example is the Interpolating FIR Filter.<br />
* General/Basic (N:M) - Provides no relation between the number of input items and the number of output items. An example is the rational resampler block which can be either a sync, decimator, or interpolator.<br />
<br />
When we insert data into our block, do we need more samples or less samples? Or put another way, should our sample rate change?<br />
<br />
What Type of Block Should we Use?<br />
* Sync Block or Basic Block<br />
<br />
<br />
=== 3.3.2. Adding Another Block to our OOT Module ===<br />
<br />
Now we know everything we need to know to create the block in gr_modtool. As a refresher, what would our gr_modtool command be?<br />
<br />
gr_modtool command...<br />
<pre><br />
gr-tutorial$ gr_modtool add -t sync -l python<br />
<br />
name: qpsk_demod_py_cb (c for complex input and b for byte/char/int8 output)<br /><br />
args: gray_code<br /><br />
QA Code: y<br />
</pre><br />
<br />
<br />
Now we have our files setup so we can begin with editing the qpsk_demod_py.py file in the /python folder<br />
<br />
=== 3.3.3. The QPSK Demodulator ===<br />
<br />
The code here is a little more than we have done before so let's take it in steps. First what do we expect our block to do? We can start with a constellation plot which shows the four different quadrants and their mappings to bit 0 and bit 1<br />
<br />
With [http://en.wikipedia.org/wiki/Phase-shift_keying#Quadrature_phase-shift_keying_.28QPSK.29 Gray coding] (adjacent only flips by 1). Note that the mapping we use here is different from the mapping on wikipedia:<br />
<br />
<br />
(-1+1j) 10 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 11 | 01 (1-1j)<br />
<br />
Without Gray coding:<br />
<br />
(-1+1j) 11 | 00 (1+1j)<br />
-----------+-----------<br />
(-1-1j) 10 | 01 (1-1j)<br />
<br />
We can see that we will need to write an if-else statement to take into account the gray_code variable. We will also need four other if-else statements inside the main if-else so that we can pick the mapping. Our pseudocode will look as follows:<br />
<br />
if gray_code<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "11" = 3<br />
else<br />
if 1+1j<br />
binary "00" = 0<br />
....<br />
elif -1-1j<br />
binary "10" = 2<br />
<br />
So we have everything we need to implement. Let's go ahead and fill in our gr_modtoool placeholders. We can begin with def init. There are three changes. How do we use the variable gray_code outside the function (similar to what we did with multiple in the last example)? What are the input and output types in [http://docs.scipy.org/doc/numpy/user/basics.types.html? numpy]<br />
<br />
Changes to def ''init''...<br />
<br />
<syntaxhighlight lang="python"><br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
</syntaxhighlight><br />
<br />
Once we have our constructor setup, we can go onto the work function. For simplicity and beauty, let us call the pseudocode we made above a function "get_minimum_distance" that takes samples as input arguments. In our multiply_py_ff example, we took all the samples and multiplied them with with out[:] = in0*self.multiple. The in0 is actually a vector so contains many samples within it. The multiply example required the same operation for each sample so it was okay to simply operate on the entire vector but now we need to have different operations per sample so what do we do?<br />
<br />
How can we operate on samples in a vector?<br />
* loops!<br />
<br />
<syntaxhighlight lang="python"><br />
for i in range(0, len(output_items[0])):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
</syntaxhighlight><br />
<br />
<br />
Now we can move onto the get_minimum_distances(self, sample) function. We already have pseudo code so the next step is translating to Python. Below is a snip of what the code can look like. Again there are multiple ways to do this<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
</syntaxhighlight><br />
Let us try to fill in the other cases for gray code and non-gray code. Below is what the entire file Python file can look like once complete:<br />
<br />
qpsk_demod_py_cb.py<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
import numpy<br />
from gnuradio import gr<br />
<br />
class qpsk_demod_py(gr.sync_block):<br />
"""<br />
docstring for block qpsk_demod_py<br />
"""<br />
def __init__(self, gray_code):<br />
self.gray_code=gray_code<br />
gr.sync_block.__init__(self,<br />
name="qpsk_demod_py",<br />
in_sig=[numpy.complex64],<br />
out_sig=[numpy.uint8])<br />
<br />
def get_minimum_distances(self, sample):<br />
if self.gray_code == 1:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 2 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 3 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
else:<br />
if (sample.imag >= 0 and sample.real >= 0):<br />
return 0 # 1+1j<br />
elif (sample.imag >= 0 and sample.real < 0):<br />
return 3 # -1+1j<br />
elif (sample.imag < 0 and sample.real < 0):<br />
return 2 # -1-1j<br />
elif (sample.imag < 0 and sample.real >= 0):<br />
return 1 # 1-1j<br />
<br />
def work(self, input_items, output_items):<br />
in0 = input_items[0]<br />
out = output_items[0]<br />
<br />
for i in range(0, len(in0)):<br />
sample = in0[i]<br />
out[i] = self.get_minimum_distances(sample)<br />
<br />
return len(output_items[0])<br />
</syntaxhighlight><br />
<br />
<br />
Now that we have code, we know what's next!<br />
<br />
=== 3.3.4. Multiple QA Tests ===<br />
<br />
We can test our qpsk_demod_py for when it is in gray_code mode and when it's not in gray_code mode. To do that we need to setup multiple tests in our single QA file. QA tests generally follow the same setup from before. We select some inputs as tests and we check them against what we expect the outputs to be. The only difference from the multiply qa test is that this qa test requires more cases. There are four quadrants we need to map and two modes so in total there are eight test cases. We can open up our qa_qpsk_demod_py_ff.py file to change things.<br />
<br />
We can copy the def test_001_t from the qa_multiply_py_ff code which is copied below:<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
def test_001_t (self):<br />
src_data = (-3, 4, -5.5, 2, 3)<br />
expected_result = (-6, 8, -11, 4, 6)<br />
src = blocks.vector_source_f (src_data)<br />
mult = multiply_py_ff (2)<br />
dst = blocks.vector_sink_f ()<br />
self.tb.connect (src, mult)<br />
self.tb.connect (mult, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
</syntaxhighlight><br />
This time we are working with a complex input so our src = blocks.vector_source_f must change. If we use the search bar in the manual we can find the other options:<br />
<br />
PIC of SEARCH<br />
<br />
b - bytes/unsigned char/int8<br /><br />
c - complex<br /><br />
f - float<br /><br />
i - int<br /><br />
s - short<br />
<br />
What do we change our source and sink vectors to?<br /><br />
src = blocks.vector_source_c (src_data)<br /><br />
dst = blocks.vector_sink_b ()<br /><br />
<br />
<br />
Before we move onto actual test cases, let us decide which mode we are testing for the test_001_t. We can create a new variable and assign it False (translates to 0) to test non-Gray code<br />
<br />
<syntaxhighlight lang="python"><br />
gray_code = False<br />
</syntaxhighlight><br />
Once we know we want to test non gray_code mappings, we can refer to our chart above and start placing in the proper inputs and outputs into the src_data and the expected_results. For instance if we were testing only two cases for non gray_code, we would do:<br />
<br />
<syntaxhighlight lang="python" line="line">src_data = ((-1-1j), (-1+1j))<br />
expected_result = (2, 3)<br />
</syntaxhighlight><br />
Last thing to do is call upon our new block in the "qpsk =" line and pass it the gray_code parameter<br />
<br />
qpsk = ?<br />
* qpsk = qpsk_demod_py_cb (gray_code)<br />
<br />
Now that we are done with the non gray_code test, we can simply create another test "def test_002_t (self):" and copy the contents underneath making sure that for this test we set gray_code = True and change the expected_result so it matches gray_code mapping. The full test is copied below:<br />
<br />
Full QA QPSK Demod Code...<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
from gnuradio import gr, gr_unittest<br />
from gnuradio import blocks<br />
import numpy<br />
from qpsk_demod_py_cb import qpsk_demod_py<br />
<br />
class qa_qpsk_demod (gr_unittest.TestCase):<br />
<br />
def setUp (self):<br />
self.tb = gr.top_block ()<br />
<br />
def tearDown (self):<br />
self.tb = None<br />
<br />
def test_001_t (self):<br />
gray_code = False<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (2, 3, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
def test_002_t (self):<br />
gray_code = True<br />
src_data = ((-1-1j), (-1+1j), (1+1j), (1-1j))<br />
expected_result = (3, 2, 0, 1)<br />
src = blocks.vector_source_c (src_data)<br />
qpsk = qpsk_demod_py (gray_code)<br />
dst = blocks.vector_sink_b ()<br />
self.tb.connect (src, qpsk)<br />
self.tb.connect (qpsk, dst)<br />
self.tb.run ()<br />
result_data = dst.data ()<br />
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)<br />
<br />
if __name__ == '__main__':<br />
gr_unittest.run(qa_qpsk_demod, "qpsk_demod_py_cb.xml")<br />
</syntaxhighlight><br />
<br />
<br />
We can then run the test in Python and all should say something similar to:<br />
<br />
Ran 2 tests in 0.005s<br />
<br />
OK<br />
<br />
So once we verify it works as expected, we can then edit our XML file so that it is usable inside GRC.<br />
<br />
=== 3.3.5. XML Mods, Installation, and Running ===<br />
<br />
This XML is very similar to the XML file for the multiply_py_ff block so all we need to do is set the gray_code parameter and pick the correct input (complex) and output (byte) types. A copy of the full XML file is below:<br />
<br />
XML File for QPSK Demod<br />
<br />
<syntaxhighlight lang="python" line="line"><br />
<?xml version="1.0"?><br />
<block><br />
<name>qpsk_demod_py</name><br />
<key>tutorial_qpsk_demod_py</key><br />
<category>tutorial</category><br />
<import>import tutorial</import><br />
<make>tutorial.qpsk_demod_py($gray_code)</make><br />
<!-- Make one 'param' node for every Parameter you want settable from the GUI.<br />
Sub-nodes:<br />
* name<br />
* key (makes the value accessible as $keyname, e.g. in the make node)<br />
* type --><br />
<param><br />
<name>Gray Code</name><br />
<key>gray_code</key><br />
<type>int</type><br />
</param><br />
<br />
<!-- Make one 'sink' node per input. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<sink><br />
<name>in</name><br />
<type>complex</type><br />
</sink><br />
<br />
<!-- Make one 'source' node per output. Sub-nodes:<br />
* name (an identifier for the GUI)<br />
* type<br />
* vlen<br />
* optional (set to 1 for optional inputs) --><br />
<!-- e.g. int, float, complex, byte, short, xxx_vector, ...--><br />
<source><br />
<name>out</name><br />
<type>byte</type><br />
</source><br />
</block><br />
<br />
</syntaxhighlight><br />
<br />
<br />
We can then install as we did for the multiply block however we need to rerun cmake in order to take into account the new block:<br />
<pre><br />
cd build<br />
cmake ../<br />
make<br />
sudo make install<br />
sudo ldconfig<br />
</pre><br />
Then we can open up our GRC file from the beginning and place our missing block we just made.<br />
<br />
What is the Expected Output?<br />
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_4.png<br />
<br />
== 3.4. Conclusion ==<br />
<br />
And that is it for now with Python. Let us know your thoughts before going on to the [[Guided_Tutorial_GNU_Radio_in_C++|C++ tutorial]].<br />
<br />
=== 3.4.1. Questions We Should Now be Able to Answer ===<br />
<br />
1. How do we set input- and output signatures in Python blocks?<br /><br />
2. Consider this I/O signature: (FIXME). Which input types and output types does it take?<br />
<br />
=== 3.4.2. Links to Further Information ===<br />
<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/BlocksCodingGuide Blocks Coding Guide]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/OutOfTreeModules Out-of-Tree Modules]<br />
* [http://gnuradio.org/redmine/projects/gnuradio/wiki/TutorialsWritePythonApplications Writing Python Applications]<br />
<br />
== 3.5. Candidates for Future Sections ==<br />
<br />
Possible topics we may want to add depending on feedback and questions on the mailing list<br /><br />
- How to add documentation to blocks<br /><br />
- Constructor arguments, History, Output Multiple<br />
<br />
<br />
-----<br />
<br />
&lt;. [[Guided_Tutorial_GRC|Previous: Working with GRC]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_C++|Next: Programming GNU Radio in C++]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Tutorial_two_3.png&diff=6407File:Tutorial two 3.png2019-12-08T11:44:29Z<p>Cmrincon: Cmrincon uploaded a new version of File:Tutorial two 3.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GRC&diff=6406Guided Tutorial GRC2019-12-08T11:36:47Z<p>Cmrincon: doesn't work in gnu3.8.0.0</p>
<hr />
<div>&lt;. [[Guided_Tutorial_Introduction|Previous: Introduction]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_Python|Next: Programming GNU Radio in Python]]<br />
<br />
= Tutorial: GNU Radio Companion =<br />
<br />
== Objectives ==<br />
<br />
* Create flowgraphs using the standard block libraries<br />
* Learn how to debug flowgraphs with the instrumentation blocks<br />
* Understand how sampling and throttle works in GNU Radio<br />
* Learn how to use the documentation to figure out block's functionality<br />
<br />
== Prerequisites ==<br />
<br />
* Basic knowledge of git<br />
* [[InstallingGR|GNU Radio 3.8.0.0 or later]]<br />
* [[Guided_Tutorial_Introduction|Tutorial 1: Intro to GNU Radio]]<br />
<br />
<br />
-----<br />
<br />
== Setting up the Tutorials ==<br />
<br />
Before we can begin, we need to mention that the solutions (including the images, grc files, and module files) are available on [https://github.com/gnuradio/gr-tutorial gr-tutorial github] if you wish to have them. We will be referencing the files but will for the most part be making our own files so that we can get practice and build intuition. Thus, we recommend not downloading and installing them until you finish these tutorials or are stuck on a step. For instructions on installing these solutions, see [[Installing the Tutorials Modules]].<br />
<br />
== Getting to Know the GRC ==<br />
<br />
We have seen in Tutorial 1 that GNU Radio is a collection of tools that can be used to develop radio systems in software as opposed to completely in hardware. In this tutorial, we start off simple and explore how to use the GNU Radio Companion (GRC), GNU Radio's graphical tool, to create different tones. We should keep in the back of our mind that GRC was created to simplify the use of GNU Radio by allowing us to create python files graphically as opposed to creating them in code alone (we will discuss this more later).<br />
<br />
The first thing to cover is the interface. There are five parts: <span style="color:gray">Library</span>, <span style="color:red">Toolbar</span>, <span style="color:green">Terminal</span>, <span style="color:blue">Workspace</span> and <span style="color:yellow"> variables.<br />
<br />
[[File:unity-2d-shell_008.png|600px|]]<br />
<br />
The tutorial is meant to be hands on, so please take a few breaks from reading here and there to experiment. We will reiterate that these tutorials as simply meant as guides and that the best way to learn something is to try it out: come up with a question, think of a possible solution, and try it out. Let us begin by starting up the GRC! This is usually done by opening up a terminal window (ctrl+alt+t in Ubuntu) and typing:<br />
<br />
<pre>$ gnuradio-companion</pre><br />
If you are unable to open GRC then you need to go back to [[InstallingGR]] and troubleshoot the installation.<br />
<br />
=== Searching for Blocks ===<br />
<br />
The Library contains the different blocks installed in the GRC block paths. Here we find blocks that are preinstalled in GNU Radio and blocks that are installed on the system. At first it seems daunting to look for blocks. For instance, if we want to generate a waveform, what category should we look in? We see there is a '''Waveform Generators''' category, okay not bad. But what if we wanted to find some way to display our data but aren't sure of the best way to display it yet? We know from tutorial 1 that this is known as a sink; however, looking through the list there is no Sinks category. The solution is to use the Search feature by either clicking the magnifying glass icon or typing Ctrl+f and then start typing a keyword to identify the block. We see a white box appear at the top of the Library with a cursor. If we type &quot;sink&quot;, we can find all the blocks that contain the words &quot;sink&quot; and the <span style="color:blue">categories</span> we will find each block in.<br />
<br />
[[File:search.png|200px|]]<br />
<br />
<span style="color:red">For now, let's add the block called '''QT GUI Time Sink'''</span> by clicking on its name and dragging it to the Workspace or double-clicking on its name for it to be placed automatically in the Workspace.<br />
<br />
=== Modifying Block Properties ===<br />
<br />
The workspace (main area of the screen) contains all of our blocks that make up our flowgraph, and inside each block we can see all the different block parameters. There is, however, one special block that each new flowgraph starts with and is required to have, called the '''Options Block'''. Let us double-click on the Options Block to examine its properties. We see a window as below:<br />
<br />
[[File:properties_options.png|400px|]]<br />
<br />
These block properties can be changed from the defaults to accomplish different tasks. Let's remove part of the current name and notice the <span style="color:red">ID turns blue</span>. This color means that the information has been edited, but has not been saved. If we go back to the '''Options Block''' properties, we can see that <span style="background:yellow">there are different tabs and one is titled documentation</span>.<br />
<br />
[[File:id_python.png|400px|]]<br />
<br />
If we read a couple lines, we can see that the <span style="color:purple">property '''ID''' is actually used for the name of the python file the flowgraph generates.</span><br />
<br />
[[File:properties_errors.png|400px|]]<br />
<br />
So now let's remove the entire ID string. Notice now that at the bottom appears an <span style="color:red">error message.</span> Also notice that the <span style="color:red">parameter '''ID''' is now red</span> to show us exactly where the error occured.<br />
<br />
To keep things organized, let us change the '''ID''' to &quot;tutorial_two_1&quot;. Let us also make sure that the property '''Generate Options''' is set to &quot;QT GUI&quot; since we are using a graphical sink. The '''ID''' field allows us to more easily manage our file space. While we save the GRC flowgraph as a <filename>.grc, generating and executing this flowgraph produces another output. GRC is a graphical interface that sits on top of the normal GNU Radio programming environment that is in Python. GRC translates the flowgraph we create in the GUI canvas here into a Python script, so when we execute a flowgraph, we are really running a Python program. The '''ID''' is used to name that Python file, saved into the same directory as the .grc file. By default, the '''ID''' is '''default''' and so it creates a file called '''default.py'''. Changing the '''ID''' allows us to change the saved file name for better file management. In GNUradio 3.8 you will get an error if you don't change the default id, so you need to change this id in order to run the flowgraph.<br />
<br />
Another result of this GRC-Python connection is that GRC is actually all Python. In fact, all entry boxes in block properties or variables that we use are interpreted as Python. That means that we can set properties using Python calls, such as calling a numpy or other GNU Radio functions. A common use of this is to call into the '''filter.firdes''' filter design tool from GNU Radio to build our filter taps.<br />
<br />
Another key to notice is the different colors present in the fields we can enter information. These actually correspond to different data types which we will cover later in this tutorial.<br />
<br />
=== My First Flowgraph ===<br />
<br />
Now that we have a better understanding of how to find blocks, how to add them to the workspace, and how to edit the block properties, let's proceed to construct the following flowgraph of a '''Signal Source''' being sent to a '''Throttle Block''' and then to our '''Time Sink''' by clicking on the data type colored ports/tabs one after the other to make connections:<br />
<br />
[[File:tutorial_two_1.png|600px|tutorial_two_1.grc]]<br />
<br />
With a usable flowgraph we can now proceed to discuss the Toolbar.<br />
<br />
A note on the '''throttle block''': What exactly this does is explained in greater detail later on in this tutorial. For now, it will suffice to know that this block throttles the flow graph to make sure it doesn't consume 100% of your CPU cycles and make your computer unresponsive.<br />
<br />
[[File:commandspace.png|600px|]]<br />
<br />
This section of the interface contains commands present in most software such as new, open, save, copy, paste. Let's begin by saving our work so far and titling our flow graph '''tutorial_two'''. Important tools here are <span style="color:blue">'''Generate''' flowgraph,</span> <span style="color:red">'''Execute''' flowgraph,</span> and '''Kill''' flowgraph all accessible through F5, F6, and F7 respectively. A good reference is available in '''Help'''-&gt;'''Types''' that shows the color mapping of types which we will look into later.<br />
<br />
=== Examining the Output ===<br />
<br />
Let's go ahead and '''Execute''' the flowgraph to see our sine wave.<br />
<br />
[[File:complex_sine.png|600px|]]<br />
<br />
We should get the above which is a complex sinusoid of the form e <sup>jwt</sup>. Let us keep things simple by avoiding complex signals for now. First we want to kill the flowgraph with the '''Kill''' flowgraph button or by simply closing the '''Time Sink''' GUI. Now is a good time to go over data types in GNU Radio by opening up the '''Help'''-&gt;'''Types''' window.<br />
<br />
[[File:types.png|250px|]]<br />
<br />
We can see common data types seen in many programming languages. We can see our blocks (blue ports) are currently '''Complex Float 32''' type which means they contain both a real and imaginary part each being a '''Float 32''' type. We can reason that when the '''Time Sink''' takes in a complex data type, it outputs both the real and imaginary part on separate channels. So let's now change the '''Signal Source''' to a float by going into its block properties and changing the '''Output Type''' parameter. <span style="color:gray">We see that its port turns orange, there is now a red arrow pointing to the '''Throttle Block''',</span> and in the Toolbar there is a <span style="color:red">red &quot;-&quot; button (red) that reads &quot;View flow graph errors&quot;. Let's go ahead and click that.</span><br />
<br />
[[File:size_mismatch.png|600px|]]<br />
<br />
We see that <span style="color:green">in the specified connection, there is size mismatch.</span> This is due to our data type size mismatch. GNU Radio will not allow us to chain together blocks of different data sizes, so let's change the data type of all of our subsequent blocks. We can now generate and execute as before.<br />
<br />
[[File:tutorial_two_2.png|600px|tutorial_two_2.grc]]<br />
<br />
We now see our sine wave on one channel. We can click on the screen and move the mouse to zoom and rescale.<br />
<br />
== A More Complex Flowgraph ==<br />
<br />
Now that we are able to create flowgraphs on our own, lets try creating a more complicated flowgraph with many specific parameters. This example flowgraph demonstrates many new concepts in GNU Radio like using tabbed windows and QT GUI Ranges. Note that not all block parameters are displayed in the main window, so use the text below (not just the screenshot) to set the parameters of each block.<br />
<br />
=== Time &amp; Frequency Flowgraph ===<br />
<br />
[[File:tutorial_two_3.png|600px|tutorial_two_3.grc]]<br />
<br />
Detailed Changes:<br /><br />
<span style="color:gray">- We are starting a new flowgraph with '''ID''' &quot;tutorial_two_3&quot;</span><br /><br />
<span style="color:blue">- In '''QT GUI Range''', '''ID''' to &quot;samp_rate&quot;, '''Default Value''' to &quot;5*freq&quot;, '''Start''' to &quot;0.5*freq&quot;, '''Stop''' to &quot;20*freq&quot;, '''Step''' to &quot;200&quot;</span><br /><br />
<span style="color:green">- In '''Variable''', '''ID''' to &quot;freq&quot;, '''Value''' to &quot;2e3&quot;</span><br /><br />
<span style="color:purple">- In '''Signal Source''', '''Frequency''' to &quot;freq&quot;, '''Waveform''' to &quot;Sine&quot;</span><br /><br />
</span><br /><br />
- In '''Throttle''', '''Sample Rate''' to 32e3 (more on why later)<br />
<br />
Once we have verified our changes, let's '''Generate''', and '''Execute'''. It should produce a window that has two tabs, one showing the time domain and one showing the frequency domain. There should also be a slider at the bottom to control the sample rate (of the signal source) in realtime. Changing this slider should change the observed frequency in the time and frequency sinks.<br />
<br />
Sampling rate is an interesting subject in GNU Radio -- and, indeed, any software radio platform. Please see the [[Guided_Tutorial_Extras_Sample_Rates|Extras on Sampling Rate]] page that explores how changing the sample rates in the above flowgraph affects the signals.<br />
<br />
== Conclusion ==<br />
<br />
And that is it for now with GRC. Let us know your thoughts before going on to the [https://wiki.gnuradio.org/index.php/Guided_Tutorial_GNU_Radio_in_Python python] tutorial.<br />
<br />
=== Some Questions We Now Know! ===<br />
<br />
1. If you put down a Signal Source and Abs block onto a canvas and connect them together without changing anything, an error occurs.<br /><br />
1a. How do we know there is an error?<br /><br />
1b. How do we figure out what the error is?<br /><br />
1c. How do we correct the error?<br />
<br />
2. Say that we have two signals in our flowgraph that we wish to multiply together.<br /><br />
2a. How would we find a block that multiplies signals?<br /><br />
2b. How do we use the multiply block in GRC?<br /><br />
2c. What else can we do and change in this block?<br />
<br />
3. If you saw a block had an unused, light gray input port on it, what kind of port would that be?<br />
<br />
4. If you run a flowgraph and see the &quot;AttributeError: 'top_block_sptr' object has no attribute 'top_layout'&quot;, what is wrong and how can you fix it?<br />
<br />
5. Signal processing questions<br />
<br />
* Say we want to process speech audio data, and we have a microphone that won't let any frequencies in higher than 8 kHz. What is the minimum sampling rate we must use?<br />
* Now we want to digitize a radio signal that goes from 99.9 MHz to 100.1 MHz. How large is the minimum applicable sampling rate?<br />
<br />
6. Answers<br />
* 16 kHz (2 * 8 kHz)<br />
* The bandwidth is 200 kHz, so we must sample at least at 400 kHz -- or 200 kHz if we have complex sampling, as we usually do in software radios.<br /><br />
<br />
<br />
=== Links to Further Resources ===<br />
<br />
Links that are accessible without knowing much about how GNU Radio interacts with code. Not necessary to proceed.<br />
<br />
* [[TutorialsCoreConcepts|Core Concepts]]<br />
* [[Hardware|Hardware]]<br />
<br />
[[Category:Guided Tutorials]]<br />
<br />
<br />
<br />
-----<br />
&lt;. [[Guided_Tutorial_Introduction|Previous: Introduction]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_Python|Next: Programming GNU Radio in Python]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GRC&diff=6405Guided Tutorial GRC2019-12-08T11:33:39Z<p>Cmrincon: doesn't work in gnu3.8.0.0</p>
<hr />
<div>&lt;. [[Guided_Tutorial_Introduction|Previous: Introduction]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_Python|Next: Programming GNU Radio in Python]]<br />
<br />
= Tutorial: GNU Radio Companion =<br />
<br />
== Objectives ==<br />
<br />
* Create flowgraphs using the standard block libraries<br />
* Learn how to debug flowgraphs with the instrumentation blocks<br />
* Understand how sampling and throttle works in GNU Radio<br />
* Learn how to use the documentation to figure out block's functionality<br />
<br />
== Prerequisites ==<br />
<br />
* Basic knowledge of git<br />
* [[InstallingGR|GNU Radio 3.8.0.0 or later]]<br />
* [[Guided_Tutorial_Introduction|Tutorial 1: Intro to GNU Radio]]<br />
<br />
<br />
-----<br />
<br />
== Setting up the Tutorials ==<br />
<br />
Before we can begin, we need to mention that the solutions (including the images, grc files, and module files) are available on [https://github.com/gnuradio/gr-tutorial gr-tutorial github] if you wish to have them. We will be referencing the files but will for the most part be making our own files so that we can get practice and build intuition. Thus, we recommend not downloading and installing them until you finish these tutorials or are stuck on a step. For instructions on installing these solutions, see [[Installing the Tutorials Modules]].<br />
<br />
== Getting to Know the GRC ==<br />
<br />
We have seen in Tutorial 1 that GNU Radio is a collection of tools that can be used to develop radio systems in software as opposed to completely in hardware. In this tutorial, we start off simple and explore how to use the GNU Radio Companion (GRC), GNU Radio's graphical tool, to create different tones. We should keep in the back of our mind that GRC was created to simplify the use of GNU Radio by allowing us to create python files graphically as opposed to creating them in code alone (we will discuss this more later).<br />
<br />
The first thing to cover is the interface. There are five parts: <span style="color:gray">Library</span>, <span style="color:red">Toolbar</span>, <span style="color:green">Terminal</span>, <span style="color:blue">Workspace</span> and <span style="color:yellow"> variables.<br />
<br />
[[File:unity-2d-shell_008.png|600px|]]<br />
<br />
The tutorial is meant to be hands on, so please take a few breaks from reading here and there to experiment. We will reiterate that these tutorials as simply meant as guides and that the best way to learn something is to try it out: come up with a question, think of a possible solution, and try it out. Let us begin by starting up the GRC! This is usually done by opening up a terminal window (ctrl+alt+t in Ubuntu) and typing:<br />
<br />
<pre>$ gnuradio-companion</pre><br />
If you are unable to open GRC then you need to go back to [[InstallingGR]] and troubleshoot the installation.<br />
<br />
=== Searching for Blocks ===<br />
<br />
The Library contains the different blocks installed in the GRC block paths. Here we find blocks that are preinstalled in GNU Radio and blocks that are installed on the system. At first it seems daunting to look for blocks. For instance, if we want to generate a waveform, what category should we look in? We see there is a '''Waveform Generators''' category, okay not bad. But what if we wanted to find some way to display our data but aren't sure of the best way to display it yet? We know from tutorial 1 that this is known as a sink; however, looking through the list there is no Sinks category. The solution is to use the Search feature by either clicking the magnifying glass icon or typing Ctrl+f and then start typing a keyword to identify the block. We see a white box appear at the top of the Library with a cursor. If we type &quot;sink&quot;, we can find all the blocks that contain the words &quot;sink&quot; and the <span style="color:blue">categories</span> we will find each block in.<br />
<br />
[[File:search.png|200px|]]<br />
<br />
<span style="color:red">For now, let's add the block called '''QT GUI Time Sink'''</span> by clicking on its name and dragging it to the Workspace or double-clicking on its name for it to be placed automatically in the Workspace.<br />
<br />
=== Modifying Block Properties ===<br />
<br />
The workspace (main area of the screen) contains all of our blocks that make up our flowgraph, and inside each block we can see all the different block parameters. There is, however, one special block that each new flowgraph starts with and is required to have, called the '''Options Block'''. Let us double-click on the Options Block to examine its properties. We see a window as below:<br />
<br />
[[File:properties_options.png|400px|]]<br />
<br />
These block properties can be changed from the defaults to accomplish different tasks. Let's remove part of the current name and notice the <span style="color:red">ID turns blue</span>. This color means that the information has been edited, but has not been saved. If we go back to the '''Options Block''' properties, we can see that <span style="background:yellow">there are different tabs and one is titled documentation</span>.<br />
<br />
[[File:id_python.png|400px|]]<br />
<br />
If we read a couple lines, we can see that the <span style="color:purple">property '''ID''' is actually used for the name of the python file the flowgraph generates.</span><br />
<br />
[[File:properties_errors.png|400px|]]<br />
<br />
So now let's remove the entire ID string. Notice now that at the bottom appears an <span style="color:red">error message.</span> Also notice that the <span style="color:red">parameter '''ID''' is now red</span> to show us exactly where the error occured.<br />
<br />
To keep things organized, let us change the '''ID''' to &quot;tutorial_two_1&quot;. Let us also make sure that the property '''Generate Options''' is set to &quot;QT GUI&quot; since we are using a graphical sink. The '''ID''' field allows us to more easily manage our file space. While we save the GRC flowgraph as a <filename>.grc, generating and executing this flowgraph produces another output. GRC is a graphical interface that sits on top of the normal GNU Radio programming environment that is in Python. GRC translates the flowgraph we create in the GUI canvas here into a Python script, so when we execute a flowgraph, we are really running a Python program. The '''ID''' is used to name that Python file, saved into the same directory as the .grc file. By default, the '''ID''' is '''default''' and so it creates a file called '''default.py'''. Changing the '''ID''' allows us to change the saved file name for better file management. In GNUradio 3.8 you will get an error if you don't change the default id, so you need to change this id in order to run the flowgraph.<br />
<br />
Another result of this GRC-Python connection is that GRC is actually all Python. In fact, all entry boxes in block properties or variables that we use are interpreted as Python. That means that we can set properties using Python calls, such as calling a numpy or other GNU Radio functions. A common use of this is to call into the '''filter.firdes''' filter design tool from GNU Radio to build our filter taps.<br />
<br />
Another key to notice is the different colors present in the fields we can enter information. These actually correspond to different data types which we will cover later in this tutorial.<br />
<br />
=== My First Flowgraph ===<br />
<br />
Now that we have a better understanding of how to find blocks, how to add them to the workspace, and how to edit the block properties, let's proceed to construct the following flowgraph of a '''Signal Source''' being sent to a '''Throttle Block''' and then to our '''Time Sink''' by clicking on the data type colored ports/tabs one after the other to make connections:<br />
<br />
[[File:tutorial_two_1.png|600px|tutorial_two_1.grc]]<br />
<br />
With a usable flowgraph we can now proceed to discuss the Toolbar.<br />
<br />
A note on the '''throttle block''': What exactly this does is explained in greater detail later on in this tutorial. For now, it will suffice to know that this block throttles the flow graph to make sure it doesn't consume 100% of your CPU cycles and make your computer unresponsive.<br />
<br />
[[File:commandspace.png|600px|]]<br />
<br />
This section of the interface contains commands present in most software such as new, open, save, copy, paste. Let's begin by saving our work so far and titling our flow graph '''tutorial_two'''. Important tools here are <span style="color:blue">'''Generate''' flowgraph,</span> <span style="color:red">'''Execute''' flowgraph,</span> and '''Kill''' flowgraph all accessible through F5, F6, and F7 respectively. A good reference is available in '''Help'''-&gt;'''Types''' that shows the color mapping of types which we will look into later.<br />
<br />
=== Examining the Output ===<br />
<br />
Let's go ahead and '''Execute''' the flowgraph to see our sine wave.<br />
<br />
[[File:complex_sine.png|600px|]]<br />
<br />
We should get the above which is a complex sinusoid of the form e <sup>jwt</sup>. Let us keep things simple by avoiding complex signals for now. First we want to kill the flowgraph with the '''Kill''' flowgraph button or by simply closing the '''Time Sink''' GUI. Now is a good time to go over data types in GNU Radio by opening up the '''Help'''-&gt;'''Types''' window.<br />
<br />
[[File:types.png|250px|]]<br />
<br />
We can see common data types seen in many programming languages. We can see our blocks (blue ports) are currently '''Complex Float 32''' type which means they contain both a real and imaginary part each being a '''Float 32''' type. We can reason that when the '''Time Sink''' takes in a complex data type, it outputs both the real and imaginary part on separate channels. So let's now change the '''Signal Source''' to a float by going into its block properties and changing the '''Output Type''' parameter. <span style="color:gray">We see that its port turns orange, there is now a red arrow pointing to the '''Throttle Block''',</span> and in the Toolbar there is a <span style="color:red">red &quot;-&quot; button (red) that reads &quot;View flow graph errors&quot;. Let's go ahead and click that.</span><br />
<br />
[[File:size_mismatch.png|600px|]]<br />
<br />
We see that <span style="color:green">in the specified connection, there is size mismatch.</span> This is due to our data type size mismatch. GNU Radio will not allow us to chain together blocks of different data sizes, so let's change the data type of all of our subsequent blocks. We can now generate and execute as before.<br />
<br />
[[File:tutorial_two_2.png|600px|tutorial_two_2.grc]]<br />
<br />
We now see our sine wave on one channel. We can click on the screen and move the mouse to zoom and rescale.<br />
<br />
== A More Complex Flowgraph ==<br />
<br />
Now that we are able to create flowgraphs on our own, lets try creating a more complicated flowgraph with many specific parameters. This example flowgraph demonstrates many new concepts in GNU Radio like using tabbed windows and QT GUI Ranges. Note that not all block parameters are displayed in the main window, so use the text below (not just the screenshot) to set the parameters of each block.<br />
<br />
=== Time &amp; Frequency Flowgraph ===<br />
<br />
[[File:tutorial_two_3.png|600px|tutorial_two_3.grc]]<br />
<br />
Detailed Changes:<br /><br />
<span style="color:gray">- We are starting a new flowgraph with '''ID''' &quot;tutorial_two_3&quot;</span><br /><br />
<span style="background:yellow">- In '''QT GUI Tab Widget''', '''ID''' to &quot;tab&quot;, '''Num Tabs''' to 2, '''Label 0''' to &quot;Time&quot;, '''Label 1''' to &quot;Frequency&quot;</span><br /><br />
<span style="color:blue">- In '''QT GUI Range''', '''ID''' to &quot;samp_rate&quot;, '''Default Value''' to &quot;5*freq&quot;, '''Start''' to &quot;0.5*freq&quot;, '''Stop''' to &quot;20*freq&quot;, '''Step''' to &quot;200&quot;</span><br /><br />
<span style="color:green">- In '''Variable''', '''ID''' to &quot;freq&quot;, '''Value''' to &quot;2e3&quot;</span><br /><br />
<span style="color:purple">- In '''Signal Source''', '''Frequency''' to &quot;freq&quot;, '''Waveform''' to &quot;Sine&quot;</span><br /><br />
<span style="color:red">- In '''QT GUI Time Sink''', '''GUI Hint''' to &quot;tab@0&quot;. In '''QT GUI Frequency Sink''', '''GUI Hint''' to &quot;tab@1&quot;</span><br /><br />
- In '''Throttle''', '''Sample Rate''' to 32e3 (more on why later)<br />
<br />
Once we have verified our changes, let's '''Generate''', and '''Execute'''. It should produce a window that has two tabs, one showing the time domain and one showing the frequency domain. There should also be a slider at the bottom to control the sample rate (of the signal source) in realtime. Changing this slider should change the observed frequency in the time and frequency sinks.<br />
<br />
Sampling rate is an interesting subject in GNU Radio -- and, indeed, any software radio platform. Please see the [[Guided_Tutorial_Extras_Sample_Rates|Extras on Sampling Rate]] page that explores how changing the sample rates in the above flowgraph affects the signals.<br />
<br />
== Conclusion ==<br />
<br />
And that is it for now with GRC. Let us know your thoughts before going on to the [https://wiki.gnuradio.org/index.php/Guided_Tutorial_GNU_Radio_in_Python python] tutorial.<br />
<br />
=== Some Questions We Now Know! ===<br />
<br />
1. If you put down a Signal Source and Abs block onto a canvas and connect them together without changing anything, an error occurs.<br /><br />
1a. How do we know there is an error?<br /><br />
1b. How do we figure out what the error is?<br /><br />
1c. How do we correct the error?<br />
<br />
2. Say that we have two signals in our flowgraph that we wish to multiply together.<br /><br />
2a. How would we find a block that multiplies signals?<br /><br />
2b. How do we use the multiply block in GRC?<br /><br />
2c. What else can we do and change in this block?<br />
<br />
3. If you saw a block had an unused, light gray input port on it, what kind of port would that be?<br />
<br />
4. If you run a flowgraph and see the &quot;AttributeError: 'top_block_sptr' object has no attribute 'top_layout'&quot;, what is wrong and how can you fix it?<br />
<br />
5. Signal processing questions<br />
<br />
* Say we want to process speech audio data, and we have a microphone that won't let any frequencies in higher than 8 kHz. What is the minimum sampling rate we must use?<br />
* Now we want to digitize a radio signal that goes from 99.9 MHz to 100.1 MHz. How large is the minimum applicable sampling rate?<br />
<br />
6. Answers<br />
* 16 kHz (2 * 8 kHz)<br />
* The bandwidth is 200 kHz, so we must sample at least at 400 kHz -- or 200 kHz if we have complex sampling, as we usually do in software radios.<br /><br />
<br />
<br />
=== Links to Further Resources ===<br />
<br />
Links that are accessible without knowing much about how GNU Radio interacts with code. Not necessary to proceed.<br />
<br />
* [[TutorialsCoreConcepts|Core Concepts]]<br />
* [[Hardware|Hardware]]<br />
<br />
[[Category:Guided Tutorials]]<br />
<br />
<br />
<br />
-----<br />
&lt;. [[Guided_Tutorial_Introduction|Previous: Introduction]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_Python|Next: Programming GNU Radio in Python]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Properties_options.png&diff=6404File:Properties options.png2019-12-07T18:21:03Z<p>Cmrincon: Cmrincon uploaded a new version of File:Properties options.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_GRC&diff=6403Guided Tutorial GRC2019-12-07T18:09:36Z<p>Cmrincon: update to v3.8.0.0</p>
<hr />
<div>&lt;. [[Guided_Tutorial_Introduction|Previous: Introduction]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_Python|Next: Programming GNU Radio in Python]]<br />
<br />
= Tutorial: GNU Radio Companion =<br />
<br />
== Objectives ==<br />
<br />
* Create flowgraphs using the standard block libraries<br />
* Learn how to debug flowgraphs with the instrumentation blocks<br />
* Understand how sampling and throttle works in GNU Radio<br />
* Learn how to use the documentation to figure out block's functionality<br />
<br />
== Prerequisites ==<br />
<br />
* Basic knowledge of git<br />
* [[InstallingGR|GNU Radio 3.8.0.0 or later]]<br />
* [[Guided_Tutorial_Introduction|Tutorial 1: Intro to GNU Radio]]<br />
<br />
<br />
-----<br />
<br />
== Setting up the Tutorials ==<br />
<br />
Before we can begin, we need to mention that the solutions (including the images, grc files, and module files) are available on [https://github.com/gnuradio/gr-tutorial gr-tutorial github] if you wish to have them. We will be referencing the files but will for the most part be making our own files so that we can get practice and build intuition. Thus, we recommend not downloading and installing them until you finish these tutorials or are stuck on a step. For instructions on installing these solutions, see [[Installing the Tutorials Modules]].<br />
<br />
== Getting to Know the GRC ==<br />
<br />
We have seen in Tutorial 1 that GNU Radio is a collection of tools that can be used to develop radio systems in software as opposed to completely in hardware. In this tutorial, we start off simple and explore how to use the GNU Radio Companion (GRC), GNU Radio's graphical tool, to create different tones. We should keep in the back of our mind that GRC was created to simplify the use of GNU Radio by allowing us to create python files graphically as opposed to creating them in code alone (we will discuss this more later).<br />
<br />
The first thing to cover is the interface. There are five parts: <span style="color:gray">Library</span>, <span style="color:red">Toolbar</span>, <span style="color:green">Terminal</span>, <span style="color:blue">Workspace</span> and <span style="color:yellow"> variables.<br />
<br />
[[File:unity-2d-shell_008.png|600px|]]<br />
<br />
The tutorial is meant to be hands on, so please take a few breaks from reading here and there to experiment. We will reiterate that these tutorials as simply meant as guides and that the best way to learn something is to try it out: come up with a question, think of a possible solution, and try it out. Let us begin by starting up the GRC! This is usually done by opening up a terminal window (ctrl+alt+t in Ubuntu) and typing:<br />
<br />
<pre>$ gnuradio-companion</pre><br />
If you are unable to open GRC then you need to go back to [[InstallingGR]] and troubleshoot the installation.<br />
<br />
=== Searching for Blocks ===<br />
<br />
The Library contains the different blocks installed in the GRC block paths. Here we find blocks that are preinstalled in GNU Radio and blocks that are installed on the system. At first it seems daunting to look for blocks. For instance, if we want to generate a waveform, what category should we look in? We see there is a '''Waveform Generators''' category, okay not bad. But what if we wanted to find some way to display our data but aren't sure of the best way to display it yet? We know from tutorial 1 that this is known as a sink; however, looking through the list there is no Sinks category. The solution is to use the Search feature by either clicking the magnifying glass icon or typing Ctrl+f and then start typing a keyword to identify the block. We see a white box appear at the top of the Library with a cursor. If we type &quot;sink&quot;, we can find all the blocks that contain the words &quot;sink&quot; and the <span style="color:blue">categories</span> we will find each block in.<br />
<br />
[[File:search.png|200px|]]<br />
<br />
<span style="color:red">For now, let's add the block called '''QT GUI Time Sink'''</span> by clicking on its name and dragging it to the Workspace or double-clicking on its name for it to be placed automatically in the Workspace.<br />
<br />
=== Modifying Block Properties ===<br />
<br />
The workspace (main area of the screen) contains all of our blocks that make up our flowgraph, and inside each block we can see all the different block parameters. There is, however, one special block that each new flowgraph starts with and is required to have, called the '''Options Block'''. Let us double-click on the Options Block to examine its properties. We see a window as below:<br />
<br />
[[File:properties_options.png|400px|]]<br />
<br />
These block properties can be changed from the defaults to accomplish different tasks. Let's remove part of the current name and notice the <span style="color:red">ID turns blue</span>. This color means that the information has been edited, but has not been saved. Let us <span style="color:green">change the parameter '''Canvas Size''' to &quot;300,300&quot;</span> and click OK. Yikes! Almost all the workspace got cutoff! Let's do a ctrl+z to go back to our default size. If we go back to the '''Options Block''' properties, we can see that <span style="background:yellow">there are different tabs and one is titled documentation</span>.<br />
<br />
[[File:id_python.png|400px|]]<br />
<br />
If we read a couple lines, we can see that the <span style="color:purple">property '''ID''' is actually used for the name of the python file the flowgraph generates.</span><br />
<br />
[[File:properties_errors.png|400px|]]<br />
<br />
So now let's remove the entire ID string. Notice now that at the bottom appears an <span style="color:red">error message.</span> Also notice that the <span style="color:red">parameter '''ID''' is now red</span> to show us exactly where the error occured.<br />
<br />
To keep things organized, let us change the '''ID''' to &quot;tutorial_two_1&quot;. Let us also make sure that the property '''Generate Options''' is set to &quot;QT GUI&quot; since we are using a graphical sink. The '''ID''' field allows us to more easily manage our file space. While we save the GRC flowgraph as a <filename>.grc, generating and executing this flowgraph produces another output. GRC is a graphical interface that sits on top of the normal GNU Radio programming environment that is in Python. GRC translates the flowgraph we create in the GUI canvas here into a Python script, so when we execute a flowgraph, we are really running a Python program. The '''ID''' is used to name that Python file, saved into the same directory as the .grc file. By default, the '''ID''' is '''default''' and so it creates a file called '''default.py'''. Changing the '''ID''' allows us to change the saved file name for better file management. In GNUradio 3.8 you will get an error if you don't change the default id, so you need to change this id in order to run the flowgraph.<br />
<br />
Another result of this GRC-Python connection is that GRC is actually all Python. In fact, all entry boxes in block properties or variables that we use are interpreted as Python. That means that we can set properties using Python calls, such as calling a numpy or other GNU Radio functions. A common use of this is to call into the '''filter.firdes''' filter design tool from GNU Radio to build our filter taps.<br />
<br />
Another key to notice is the different colors present in the fields we can enter information. These actually correspond to different data types which we will cover later in this tutorial.<br />
<br />
=== My First Flowgraph ===<br />
<br />
Now that we have a better understanding of how to find blocks, how to add them to the workspace, and how to edit the block properties, let's proceed to construct the following flowgraph of a '''Signal Source''' being sent to a '''Throttle Block''' and then to our '''Time Sink''' by clicking on the data type colored ports/tabs one after the other to make connections:<br />
<br />
[[File:tutorial_two_1.png|600px|tutorial_two_1.grc]]<br />
<br />
With a usable flowgraph we can now proceed to discuss the Toolbar.<br />
<br />
A note on the '''throttle block''': What exactly this does is explained in greater detail later on in this tutorial. For now, it will suffice to know that this block throttles the flow graph to make sure it doesn't consume 100% of your CPU cycles and make your computer unresponsive.<br />
<br />
[[File:commandspace.png|600px|]]<br />
<br />
This section of the interface contains commands present in most software such as new, open, save, copy, paste. Let's begin by saving our work so far and titling our flow graph '''tutorial_two'''. Important tools here are <span style="color:blue">'''Generate''' flowgraph,</span> <span style="color:red">'''Execute''' flowgraph,</span> and '''Kill''' flowgraph all accessible through F5, F6, and F7 respectively. A good reference is available in '''Help'''-&gt;'''Types''' that shows the color mapping of types which we will look into later.<br />
<br />
=== Examining the Output ===<br />
<br />
Let's go ahead and '''Execute''' the flowgraph to see our sine wave.<br />
<br />
[[File:complex_sine.png|600px|]]<br />
<br />
We should get the above which is a complex sinusoid of the form e <sup>jwt</sup>. Let us keep things simple by avoiding complex signals for now. First we want to kill the flowgraph with the '''Kill''' flowgraph button or by simply closing the '''Time Sink''' GUI. Now is a good time to go over data types in GNU Radio by opening up the '''Help'''-&gt;'''Types''' window.<br />
<br />
[[File:types.png|250px|]]<br />
<br />
We can see common data types seen in many programming languages. We can see our blocks (blue ports) are currently '''Complex Float 32''' type which means they contain both a real and imaginary part each being a '''Float 32''' type. We can reason that when the '''Time Sink''' takes in a complex data type, it outputs both the real and imaginary part on separate channels. So let's now change the '''Signal Source''' to a float by going into its block properties and changing the '''Output Type''' parameter. <span style="color:gray">We see that its port turns orange, there is now a red arrow pointing to the '''Throttle Block''',</span> and in the Toolbar there is a <span style="color:red">red &quot;-&quot; button (red) that reads &quot;View flow graph errors&quot;. Let's go ahead and click that.</span><br />
<br />
[[File:size_mismatch.png|600px|]]<br />
<br />
We see that <span style="color:green">in the specified connection, there is size mismatch.</span> This is due to our data type size mismatch. GNU Radio will not allow us to chain together blocks of different data sizes, so let's change the data type of all of our subsequent blocks. We can now generate and execute as before.<br />
<br />
[[File:tutorial_two_2.png|600px|tutorial_two_2.grc]]<br />
<br />
We now see our sine wave on one channel. We can click on the screen and move the mouse to zoom and rescale.<br />
<br />
== A More Complex Flowgraph ==<br />
<br />
Now that we are able to create flowgraphs on our own, lets try creating a more complicated flowgraph with many specific parameters. This example flowgraph demonstrates many new concepts in GNU Radio like using tabbed windows and QT GUI Ranges. Note that not all block parameters are displayed in the main window, so use the text below (not just the screenshot) to set the parameters of each block.<br />
<br />
=== Time &amp; Frequency Flowgraph ===<br />
<br />
[[File:tutorial_two_3.png|600px|tutorial_two_3.grc]]<br />
<br />
Detailed Changes:<br /><br />
<span style="color:gray">- We are starting a new flowgraph with '''ID''' &quot;tutorial_two_3&quot;</span><br /><br />
<span style="background:yellow">- In '''QT GUI Tab Widget''', '''ID''' to &quot;tab&quot;, '''Num Tabs''' to 2, '''Label 0''' to &quot;Time&quot;, '''Label 1''' to &quot;Frequency&quot;</span><br /><br />
<span style="color:blue">- In '''QT GUI Range''', '''ID''' to &quot;samp_rate&quot;, '''Default Value''' to &quot;5*freq&quot;, '''Start''' to &quot;0.5*freq&quot;, '''Stop''' to &quot;20*freq&quot;, '''Step''' to &quot;200&quot;</span><br /><br />
<span style="color:green">- In '''Variable''', '''ID''' to &quot;freq&quot;, '''Value''' to &quot;2e3&quot;</span><br /><br />
<span style="color:purple">- In '''Signal Source''', '''Frequency''' to &quot;freq&quot;, '''Waveform''' to &quot;Sine&quot;</span><br /><br />
<span style="color:red">- In '''QT GUI Time Sink''', '''GUI Hint''' to &quot;tab@0&quot;. In '''QT GUI Frequency Sink''', '''GUI Hint''' to &quot;tab@1&quot;</span><br /><br />
- In '''Throttle''', '''Sample Rate''' to 32e3 (more on why later)<br />
<br />
Once we have verified our changes, let's '''Generate''', and '''Execute'''. It should produce a window that has two tabs, one showing the time domain and one showing the frequency domain. There should also be a slider at the bottom to control the sample rate (of the signal source) in realtime. Changing this slider should change the observed frequency in the time and frequency sinks.<br />
<br />
Sampling rate is an interesting subject in GNU Radio -- and, indeed, any software radio platform. Please see the [[Guided_Tutorial_Extras_Sample_Rates|Extras on Sampling Rate]] page that explores how changing the sample rates in the above flowgraph affects the signals.<br />
<br />
== Conclusion ==<br />
<br />
And that is it for now with GRC. Let us know your thoughts before going on to the [https://wiki.gnuradio.org/index.php/Guided_Tutorial_GNU_Radio_in_Python python] tutorial.<br />
<br />
=== Some Questions We Now Know! ===<br />
<br />
1. If you put down a Signal Source and Abs block onto a canvas and connect them together without changing anything, an error occurs.<br /><br />
1a. How do we know there is an error?<br /><br />
1b. How do we figure out what the error is?<br /><br />
1c. How do we correct the error?<br />
<br />
2. Say that we have two signals in our flowgraph that we wish to multiply together.<br /><br />
2a. How would we find a block that multiplies signals?<br /><br />
2b. How do we use the multiply block in GRC?<br /><br />
2c. What else can we do and change in this block?<br />
<br />
3. If you saw a block had an unused, light gray input port on it, what kind of port would that be?<br />
<br />
4. If you run a flowgraph and see the &quot;AttributeError: 'top_block_sptr' object has no attribute 'top_layout'&quot;, what is wrong and how can you fix it?<br />
<br />
5. Signal processing questions<br />
<br />
* Say we want to process speech audio data, and we have a microphone that won't let any frequencies in higher than 8 kHz. What is the minimum sampling rate we must use?<br />
* Now we want to digitize a radio signal that goes from 99.9 MHz to 100.1 MHz. How large is the minimum applicable sampling rate?<br />
<br />
6. Answers<br />
* 16 kHz (2 * 8 kHz)<br />
* The bandwidth is 200 kHz, so we must sample at least at 400 kHz -- or 200 kHz if we have complex sampling, as we usually do in software radios.<br /><br />
<br />
<br />
=== Links to Further Resources ===<br />
<br />
Links that are accessible without knowing much about how GNU Radio interacts with code. Not necessary to proceed.<br />
<br />
* [[TutorialsCoreConcepts|Core Concepts]]<br />
* [[Hardware|Hardware]]<br />
<br />
[[Category:Guided Tutorials]]<br />
<br />
<br />
<br />
-----<br />
&lt;. [[Guided_Tutorial_Introduction|Previous: Introduction]]<br />
&gt;. [[Guided_Tutorial_GNU_Radio_in_Python|Next: Programming GNU Radio in Python]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Unity-2d-shell_008.png&diff=6402File:Unity-2d-shell 008.png2019-12-07T17:28:17Z<p>Cmrincon: Cmrincon uploaded a new version of File:Unity-2d-shell 008.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Unity-2d-shell_008.png&diff=6401File:Unity-2d-shell 008.png2019-12-07T17:26:51Z<p>Cmrincon: Cmrincon uploaded a new version of File:Unity-2d-shell 008.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Types.png&diff=6400File:Types.png2019-12-07T17:06:52Z<p>Cmrincon: Cmrincon uploaded a new version of File:Types.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Tutorial_two_3.png&diff=6399File:Tutorial two 3.png2019-12-07T17:01:08Z<p>Cmrincon: Cmrincon uploaded a new version of File:Tutorial two 3.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Tutorial_two_3.png&diff=6398File:Tutorial two 3.png2019-12-07T17:00:13Z<p>Cmrincon: Cmrincon uploaded a new version of File:Tutorial two 3.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Tutorial_two_2.png&diff=6397File:Tutorial two 2.png2019-12-07T16:17:30Z<p>Cmrincon: Cmrincon uploaded a new version of File:Tutorial two 2.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Size_mismatch.png&diff=6396File:Size mismatch.png2019-12-07T16:13:35Z<p>Cmrincon: Cmrincon uploaded a new version of File:Size mismatch.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Size_mismatch.png&diff=6395File:Size mismatch.png2019-12-07T16:11:24Z<p>Cmrincon: Cmrincon uploaded a new version of File:Size mismatch.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Commandspace.png&diff=6394File:Commandspace.png2019-12-07T16:01:33Z<p>Cmrincon: Cmrincon uploaded a new version of File:Commandspace.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Tutorial_two_1.png&diff=6393File:Tutorial two 1.png2019-12-07T15:57:54Z<p>Cmrincon: Cmrincon uploaded a new version of File:Tutorial two 1.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Properties_errors.png&diff=6392File:Properties errors.png2019-12-07T15:51:16Z<p>Cmrincon: Cmrincon uploaded a new version of File:Properties errors.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Id_python.png&diff=6391File:Id python.png2019-12-07T15:46:17Z<p>Cmrincon: Cmrincon uploaded a new version of File:Id python.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Search.png&diff=6390File:Search.png2019-12-07T15:30:16Z<p>Cmrincon: Cmrincon uploaded a new version of File:Search.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Unity-2d-shell_008.png&diff=6389File:Unity-2d-shell 008.png2019-12-07T15:22:23Z<p>Cmrincon: Cmrincon uploaded a new version of File:Unity-2d-shell 008.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Fg3-dialtone.png&diff=6388File:Fg3-dialtone.png2019-12-07T12:48:18Z<p>Cmrincon: Cmrincon uploaded a new version of File:Fg3-dialtone.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Fg2-fftvec.png&diff=6387File:Fg2-fftvec.png2019-12-07T12:46:59Z<p>Cmrincon: Cmrincon uploaded a new version of File:Fg2-fftvec.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=Guided_Tutorial_Introduction&diff=6386Guided Tutorial Introduction2019-12-07T12:44:49Z<p>Cmrincon: updated to v3.8.0.0</p>
<hr />
<div>&gt;. [[Guided_Tutorial_GRC|Next: Working with GRC]]<br />
<br />
= Introduction to GNU Radio and Software Radio =<br />
== What is GNU Radio? ==<br />
<br />
GNU Radio is a framework that enables users to design, simulate, and deploy highly capable real-world radio systems. It is a highly modular, "flowgraph"-oriented framework that comes with a comprehensive library of processing blocks that can be readily combined to make complex signal processing applications.<br />
<br />
GNU Radio has been used for a huge array of real-world radio applications, including audio processing, mobile communications, tracking satellites, radar systems, GSM networks, Digital Radio Mondiale, and much more - all in computer software.<br />
<br />
It is, by itself, not a solution to talk to any specific hardware. Nor does it provide out-of-the-box applications for specific radio communications standards (e.g., 802.11, ZigBee, LTE, etc.,), but it can be (and has been) used to develop implementations of basically any band-limited communication standard.<br />
== Why would I want GNU Radio? ==<br />
<br />
Formerly, when developing radio communication devices, the engineer had to develop a specific circuit for detection of a specific signal class, design a specific integrated circuit that would be able to decode or encode that particular transmission and debug these using costly equipment.<br />
<br />
Software-Defined Radio (SDR) takes the analog signal processing and moves it, as far as physically and economically feasible, to processing the radio signal on a computer using algorithms in software.<br />
<br />
You can, of course, use your computer-connected radio device in a program you write from scratch, concatenating algorithms as you need them and moving data in and out yourself. But this quickly becomes cumbersome: Why are you re-implementing a standard filter? Why do you have to care how data moves between different processing blocks? Wouldn't it be better to use highly optimized and peer-reviewed implementations rather than writing things yourself? And how do you get your program to scale well on a multi-core architectures but also run well on an embedded device consuming but a few watts of power? Do you really want to write all the GUIs yourself?<br />
<br />
Enter GNU Radio: A framework dedicated to writing signal processing applications for commodity computers. GNU Radio wraps functionality in easy-to-use reusable blocks, offers excellent scalability, provides an extensive library of standard algorithms, and is heavily optimized for a large variety of common platforms. It also comes with a large set of examples to get you started.<br />
<br />
== Digital Signal Processing ==<br />
<br />
As a software framework, GNU Radio works on digitized signals to generate communication functionality using general-purpose computers.<br />
=== A little signal theory ===<br />
<br />
Doing signal processing in software requires the signal to be digital. But what is a digital signal?<br />
<br />
To understand better, let's look at a common "signal" scenario: Recording voice for transmission using a cellphone.<br />
<br />
A personal physically speaking creates a sound 'signal' - the signal, in this case, is comprised of waves of varying air pressure being generated by the vocal chords of a human. A time-varying physical quantity, like the air pressure, is what is defined as a signal.<br />
<br />
[[File:sound_vocal.png|sound_vocal.png]]<br />
<br />
When the waves reach the microphone, it converts the varying pressure into an electrical signal, a variable voltage:<br />
<br />
[[File:p_to_u.png|p_to_u.png]]<br />
<br />
Now that the signal is electrical, we can work with it. The audio signal, at this point, is analog - a computer can't yet deal with it; for computational processing, a signal has to be digital, which means two things:<br />
<br />
It can only be one of a limited number of values.<br />
It only exists for a non-infinite amount of time.<br />
<br />
[[File:cont_to_digital.png|cont_to_digital.png]]<br />
<br />
This digital signal can thus be represented by a sequence of numbers, called samples. A fixed time interval between samples leads to a signal sampling rate.<br />
<br />
The process of taking a physical quantity (voltage) and converting it to digital samples is done by an Analog-to-Digital Converter (ADC). The complementary device, a Digital-to-Analog Converter (DAC), takes numbers from a digital computer and converts them to an analog signal.<br />
<br />
Now that we have a sequence of numbers, our computer can do anything with it. It might, for example, apply digital filters, compress it, recognize speech, or transmit the signal using a digital link.<br />
=== Applying Digital Signal Processing to Radio Transmissions ===<br />
<br />
The same principles as for sounds can be applied to radio waves:<br />
<br />
A signal, here electromagnetic waves, can be converted into a varying voltage using an antenna.<br />
<br />
[[File:antenna.png|antenna.png]]<br />
<br />
This electrical signal is then on a 'carrier frequency', which is usually several Mega- or even Gigahertz.<br />
<br />
Using different types of receivers (e.g. Superheterodyne Receiver, Direct Conversion, Low Intermediate Frequency Receivers), which can be acquired commercially as dedicated software radio peripherals, are already available to users (e.g. amateur radio receivers connected to sound cards) or can be obtained when re-purposing cheaply available consumer digital TV receivers (the notorious [https://www.rtl-sdr.com/about-rtl-sdr/ RTL-SDR] project).<br />
<br />
== A modular, flowgraph based Approach to Digital Signal Processing ==<br />
<br />
To process digital signals, it is straight-forward to think of the individual processing stages (filtering, correction, analysis, detection...) as processing blocks, which can be connected using simple flow-indicating arrows:<br />
<br />
[[File:twoblocks_arrow.png|twoblocks_arrow.png]]<br />
<br />
When building a signal processing application, one will build up a complete graph of blocks. Such a graph is called flowgraph in GNU Radio.<br />
<br />
[[File:example_flowgraph.png|example_flowgraph.png]]<br />
<br />
GNU Radio is a framework to develop these processing blocks and create flowgraphs, which comprise radio processing applications.<br />
<br />
As a GNU Radio user, you can combine existing blocks into a high-level flowgraph that does something as complex as receiving digitally modulated signals and GNU Radio will automatically move the signal data between these and cause processing of the data when it is ready for processing.<br />
<br />
GNU Radio comes with a large set of existing blocks. Just to give you but a small excerpt of what's available in a standard installation, here's some of the most popular block categories and a few of their members:<br />
<br />
* Waveform Generators<br />
** Constant Source<br />
** Noise Source<br />
** Signal Source (e.g. Sine, Square, Saw Tooth)<br />
<br />
* Modulators<br />
** AM Demod<br />
** Continuous Phase Modulation<br />
** PSK Mod / Demod<br />
** GFSK Mod / Demod<br />
** GMSK Mod / Demod<br />
** QAM Mod / Demod<br />
** WBFM Receive<br />
** NBFM Receive<br />
<br />
* Instrumentation (i.e., GUIs)<br />
** Constellation Sink<br />
** Frequency Sink<br />
** Histogram Sink<br />
** Number Sink<br />
** Time Raster Sink<br />
** Time Sink<br />
** Waterfall Sink<br />
<br />
* Math Operators<br />
** Abs<br />
** Add<br />
** Complex Conjugate<br />
** Divide<br />
** Integrate<br />
** Log10<br />
** Multiply<br />
** RMS<br />
** Subtract<br />
<br />
* Channel Models<br />
** Channel Model<br />
** Fading Model<br />
** Dynamic Channel Model<br />
** Frequency Selective Fading Model<br />
<br />
* Filters<br />
** Band Pass / Reject Filter<br />
** Low / High Pass Filter<br />
** IIR Filter<br />
** Generic Filterbank<br />
** Hilbert<br />
** Decimating FIR Filter<br />
** Root Raised Cosine Filter<br />
** FFT Filter<br />
<br />
* Fourier Analysis<br />
** FFT<br />
** Log Power FFT<br />
** Goertzel (Resamplers)<br />
** Fractional Resampler<br />
** Polyphase Arbitrary Resampler<br />
** Rational Resampler (Synchronizers)<br />
** Clock Recovery MM<br />
** Correlate and Sync<br />
** Costas Loop<br />
** FLL Band-Edge<br />
** PLL Freq Det<br />
** PN Correlator<br />
** Polyphase Clock Sync<br />
<br />
Using these blocks, many standard tasks, like normalizing signals, synchronization, measurements, and visualization can be done by just connecting the appropriate block to your signal processing flow graph.<br />
<br />
Also, you can write your own blocks, that either combine existing blocks with some intelligence to provide new functionality together with some logic, or you can develop your own block that operates on the input data and outputs data.<br />
<br />
Thus, GNU Radio is mainly a framework for the development of signal processing blocks and their interaction. It comes with an extensive standard library of blocks, and there are a lot of systems available that a developer might build upon. However, GNU Radio itself is not a software that is ready to do something specific -- it's the user's job to build something useful out of it, though it already comes with a lot of useful working examples. Think of it as a set of building blocks.<br />
<br />
&gt;. [[Guided_Tutorial_GRC|Next: Working with GRC]]<br />
<br />
[[Category:Guided Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Example_flowgraph.png&diff=6385File:Example flowgraph.png2019-12-07T12:33:37Z<p>Cmrincon: Cmrincon uploaded a new version of File:Example flowgraph.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Twoblocks_arrow.png&diff=6384File:Twoblocks arrow.png2019-12-07T12:30:02Z<p>Cmrincon: Cmrincon uploaded a new version of File:Twoblocks arrow.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Twoblocks_arrow.png&diff=6383File:Twoblocks arrow.png2019-12-07T12:27:36Z<p>Cmrincon: Cmrincon uploaded a new version of File:Twoblocks arrow.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Twoblocks_arrow.png&diff=6382File:Twoblocks arrow.png2019-12-07T12:24:11Z<p>Cmrincon: Cmrincon uploaded a new version of File:Twoblocks arrow.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=File:Twoblocks_arrow.png&diff=6381File:Twoblocks arrow.png2019-12-07T12:17:10Z<p>Cmrincon: Cmrincon uploaded a new version of File:Twoblocks arrow.png</p>
<hr />
<div></div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=TutorialsQTGUI&diff=6380TutorialsQTGUI2019-11-27T20:30:55Z<p>Cmrincon: update to version 3.8.0.0</p>
<hr />
<div>= Tutorial - Using the QT GUI Blocks in GNU Radio =<br />
<br />
== Basics ==<br />
<br />
This document demonstrates how to use the QT GUI features in GNU Radio. There is one main QT interface defined that comes as either a complex sink or floating point sink, they are called:<br />
<br />
* '''complex sink''': qtgui.sink_c<br /><br />
* '''float sink''': qtgui.sink_f<br />
<br />
As this shows, the sinks are located in the module '''gnuradio.qtgui''' and is imported into Python with:<br />
<br />
<pre>from gnuradio import qtgui</pre><br />
Both the complex and floating point versions of the sink take the same arguments using the following class constructor:<br />
<br />
<pre> <br />
qtgui_make_sink_X (int fftsize, int wintype, double fc=0, <br />
double bandwidth=1.0, const std::string &amp;name=&quot;Spectrum Display&quot;,<br />
bool plotfreq=true, bool plotwaterfall=true, bool plotwaterfall3d=true,<br />
bool plottime=true, bool plotconst=true, bool use_openGL=true,<br />
QWidget *parent=NULL)</pre><br />
The following describes the arguments meanings more:<br />
<br />
* '''fftsize''': initial FFT size<br /><br />
* '''wintype''': initial FFT window type. These can be taken from gr.firdes as WIN_BLACKMAN, WIN_BLACKMAN_HARRIS, WIN_HAMMING, WIN_HANN, WIN_KAISER, WIN_RECTANGULAR WIN_BLACKMAN_HARRIS is recommended if you don't have specific windowing needs<br /><br />
* '''fc''': center frequency for the x-axis display<br /><br />
* '''bandwidth''': sets the x-axis range around fc<br /><br />
* '''name''': The title of the GUI object in the title bar<br /><br />
* '''plotfreq''': Display frequency window?<br /><br />
* '''plotwaterfall''': Display waterfall window?<br /><br />
* '''plotwaterfall3d''': Display 3D waterfall window?<br /><br />
* '''plottime''': Display time window?<br /><br />
* '''plotconst''': Display constellation window?<br /><br />
* '''parent''': a parent widget for this object to be put into as a child<br />
<br />
== Prerequisites ==<br />
<br />
If you have successfully installed the gr-qtgui module with GNU Radio, then all of the prerequisites should be installed. Just in case, the Python modules you will need to run a QT GUI application are:<br />
<br />
* PyQt5<br /><br />
* SIP<br />
<br />
It is also recommended that you get PyQWT5 to be able to build more extensive and nicer looking GUIs.<br />
<br />
== Example 1: Seeing the GUI ==<br />
<br />
The first step is to be able to create a qtgui sink and display it on screen. This is both the simplest and most useful for debugging. By understanding how this example works, you can go and easily add a GUI to any existing GNU Radio application to see the signals at any point in the flow graph.<br />
<br />
Here is the full code:<br />
<br />
<pre>#!/usr/bin/env python3<br />
from PyQt5 import Qt<br />
from gnuradio import gr<br />
from gnuradio import qtgui<br />
from gnuradio import analog<br />
from gnuradio import blocks<br />
from gnuradio.filter import firdes<br />
import sys, sip<br />
<br />
class my_tb(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
# Make a local QtApp so we can start it from our side<br />
self.qapp = Qt.QApplication(sys.argv)<br />
<br />
samp_rate = 1e6<br />
fftsize = 2048<br />
<br />
self.src = analog.sig_source_c(samp_rate, analog.GR_SIN_WAVE, 0.1, 1, 0)<br />
self.nse = analog.noise_source_c(analog.GR_GAUSSIAN, 0.1)<br />
self.add = blocks.add_cc()<br />
self.thr = blocks.throttle(gr.sizeof_gr_complex, samp_rate, True)<br />
<br />
self.snk = qtgui.sink_c(<br />
fftsize, #fftsize<br />
firdes.WIN_BLACKMAN_hARRIS, #wintype<br />
0, #fc<br />
samp_rate, #bw<br />
"", #name<br />
True, #plotfreq<br />
True, #plotwaterfall<br />
True, #plottime<br />
True, #plotconst<br />
)<br />
<br />
self.connect(self.src, (self.add, 0))<br />
self.connect(self.nse, (self.add, 1))<br />
self.connect(self.add, self.thr, self.snk)<br />
<br />
# Tell the sink we want it displayed<br />
self.pyobj = sip.wrapinstance(self.snk.pyqwidget(), Qt.QWidget)<br />
self.pyobj.show()<br />
<br />
def main():<br />
tb = my_tb()<br />
tb.start()<br />
tb.qapp.exec_()<br />
<br />
if __name__ == "__main__":<br />
try:<br />
main()<br />
except KeyboardInterrupt:<br />
pass</pre><br />
Lines 2 - 8 just set up the environment and modules, including the PyQT modules we require. Lines 10 - 12 simply set up the &quot;my_tb&quot; class as a GNU Radio top block class and initialize it.<br />
<br />
Line 15 is the first deviation from normal GNU Radio flow graphs. This line gets a reference to the qApp, which is QT global application element. Right now, it is enough to know that we require it, but we won't do anything with it until later.<br />
<br />
Line 18 just sets a variable for the FFT size. Lines 20 - 25 build the blocks of the flow graph, including the qtgui block. In this flow graph, we create a sine wave and add noise to it. The noise is to make sure the signal is constantly changing in the display as a simple sine wave looks like nothing changes between frames. There is a threshold block so the GUI is not trying to run at full speed, too. Finally, the sink is a complex qtgui sink with just the first two arguments given. As can be seen in the class constructor listed above, all of the other arguments have defaults, so we will use these for now.<br />
<br />
Lines 37 - 39 just connect all of the blocks. Like any other block, the qtgui sink is added as just another connection.<br />
<br />
Lines 42 and 43 are specific to making the qtgui blocks visible. We have to use the &quot;show&quot; operation on the qtgui block, but first, we have to convert it to a Python object. Line 42 does the conversion by using the SIP wrapper interface to go from a PyObject in C++ to a native Python object as a QWidget. The &quot;pyqwidget&quot; method of the qtgui sink passes the proper object pointer back to allow this handling. Now, the Python object has the &quot;show()&quot; method required to display the sink that is called in Line 43. Without this, the code would run, but we would see nothing displayed.<br />
<br />
The final bit of tickery that deviates from a normal flow graph is how we tell the class to run. Line 46 instantiates an object of our &quot;my_tb&quot; class. Instead of calling &quot;run()&quot; on this object, though, we only call &quot;start()&quot; in line 47, which is non-blocking (run() performs both a start() and wait() call). We then tell the qApp that we got a reference to in Line 15 to execute with the &quot;exec_()&quot; method on line 48. This starts the QT runtime engine to handle the display and controls of the QT widgets. Now, when we run the program, we should see a QT application with a widget displaying the frequency domain. [SHOWPCITURE]<br />
<br />
The big take-aways message here is the relatively minor changes required to make a qtgui application from a flow graph.<br />
<br />
* Get a reference to the qApp pointer<br /><br />
* Build and connect the qtgui sink<br /><br />
* Use sip to get a wrapped instance in Python of the block and call &quot;show()&quot; on it<br /><br />
* Use the &quot;start()&quot; method of the top block class followed by a call to &quot;exec_()&quot; on the qApp to run the QT application<br />
<br />
[[Category:Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=TutorialsWritePythonApplications&diff=6379TutorialsWritePythonApplications2019-11-24T19:35:24Z<p>Cmrincon: updated to version 3.8.0.0</p>
<hr />
<div>= Introduction =<br />
<br />
Welcome, GNU Radio beginners. If you are reading this tutorial, you probably already have some very basic knowledge about how GNU Radio works, what it is and what it can do - and now you want to enter this exciting world of Open Source digital signal processing (DSP) yourself.<br />
<br />
This is a tutorial on how to write applications for GNU Radio in Python using the version 3.7 API. It is no introduction to programming, software radio or signal processing, nor does it cover how to extend GNU Radio by creating new blocks or adding code to the source tree. If you have some background in the mentioned topics and are starting to work with GNU Radio, this probably is the correct tutorial for you. If you don't know what a Software Radio is or what a FIR filter does, you should probably go a few steps back and get a more solid background on signal processing theory. But don't let this discourage you - the best way to learn something is by trying it out.<br />
<br />
Although this tutorial is designed to make your introduction to GNU Radio as easy as possible, it is not a definitive guide. In fact, I might sometimes simply not tell the real truth to make explanations easier. I might even contradict myself in later chapters. Usage of brain power is still necessary to develop GNU Radio applications.<br />
<br />
= Preliminaries =<br />
<br />
Before you get started with this tutorial, make sure your GNU Radio installation is ready and working. You don't necessarily need a USRP, but some kind of source and sink (USRP, audio or other hardware) is helpful, although not strictly required. If the GNU Radio examples work (such as dial_tone.py in gr-audio/examples/python), you're ready to go.<br />
<br />
You should also have some background in programming - but don't worry if you've never programmed Python, it is a very easy language to learn.<br />
<br />
= Understanding flow graphs =<br />
<br />
Before we start banging out code, first we need to understand the most basic concepts about GNU Radio: flow graphs (as in graph theory) and blocks. Many GNU Radio applications contain nothing other than a flow graph. The nodes of such a graph are called blocks, and the data flows along the edges.<br />
<br />
Any actual signal processing is done in the blocks. Ideally, every block does exactly one job - this way GNU Radio stays modular and flexible. Blocks are written in C++; writing new blocks is not very difficult (but explained elsewhere).<br />
<br />
The data passing between blocks can be of any kind - practically any type of data you can define in C++ is possible. In practice, the most common data types are complex and real short or long integers and floating point values as most of the time, data passing from one block to the next will either be samples or bits.<br />
<br />
== Examples ==<br />
<br />
In order to illuminate this diffuse topic a little, let's start with some examples:<br />
<br />
'''Low-pass filtered audio recorder'''<br />
<br />
<pre> +-----+ +-----+ +----------------+<br />
| Mic +--+ LPF +--+ Record to file |<br />
+-----+ +-----+ +----------------+</pre><br />
First, an audio signal from a microphone is recorded by your PCs sound card and converted into a digital signal. The samples are streamed to the next block, the low pass filter (LPF), which could be implemented as an FIR filter. The filtered signal is passed on to the final block, which records the filtered audio signal into a file.<br />
<br />
This is a simple, yet complete flow graph. The first and last block serve a special purpose: they operate as source and sink. Every flow graph needs at least one source and sink to be able to function.<br />
<br />
'''Dial tone generator'''<br />
<br />
<pre> +------------------------+<br />
| Sine generator (350Hz) +---+<br />
+------------------------+ | +------------+<br />
+---+ |<br />
| Audio sink |<br />
+---+ |<br />
+------------------------+ | +------------+<br />
| Sine generator (440Hz) +---+<br />
+------------------------+</pre><br />
This simple example is often called the &quot;Hello World of GNU Radio&quot;. Other than the first example, it has two sources. The sink, on the other hand, has two inputs - in this case for the left and right channel of the sound card. Code for this example is available at gr-audio/examples/python/dial_tone.py.<br />
<br />
'''QPSK Demodulator'''<br />
<br />
<pre> +-------------+ +----------------+ +------------------+<br />
| USRP Source +--+ Frequency sync +--+ Matched filter |<br />
+-------------+ +----------------+ +-----------+------+<br />
| COMPLEX SAMPLES<br />
+-------------+------+<br />
| Symbol demodulator |<br />
+-------------+------+<br />
| COMPLEX SYMBOLS<br />
+-----------------+ +-----------------+ +------+------+<br />
| Source decoder +--+ Channel decoder +--+ Bit mapping |<br />
+--------+--------+ +-----------------+ +-------------+<br />
| BITS<br />
+--------+--------+<br />
| Application | DATA<br />
+-----------------+</pre><br />
This example is a bit more sophisticated, but should look quite familiar to RF engineers. In this case, the source is a USRP which is connected to an antenna. This kind of source sends complex samples to the following blocks.<br />
<br />
The interesting part about this kind of flow graph is that the data types change during the flow graph: at first, complex baseband samples are passed along. Then, complex symbols are gathered from the signal. Next, these symbols are turned into bits which again are processed further. Finally, the decoded bits are passed to some application which makes use of the data.<br />
<br />
'''Walkie Talkie'''<br />
<br />
<pre> +--------------+ +------------------+ +---------+ +------------+<br />
| USRP Source +--+ NBFM Demodulator +--+ Squelch +--+ Audio Sink |<br />
+--------------+ +------------------+ +---------+ +------------+<br />
+--------------+ +----------------+ +------------+<br />
| Audio Source +----------+ NBFM Modulator +---------+ USRP Sink |<br />
+--------------+ +----------------+ +------------+</pre><br />
This applications consists of two separate flow graphs, both running in parallel. One of them deals with the Tx path, the other with the Rx path. This kind of application would require some extra code (outside the flow graphs) to mute one path while the other is active. Both flow graphs still require at least one source and sink, each. You can find a GNU Radio application that does this (only a bit more sophisticated) at gr-uhd/examples/python/usrp_nbfm_ptt.py.<br />
<br />
== Summary ==<br />
<br />
This concludes the chapter about flow graphs. Here's a quick summary about the most vital points you really need to know:<br />
<br />
* All signal processing in GNU Radio is done through flow graphs.<br />
* A flow graph consists of blocks. A block does one signal processing operation, such as filtering, adding signals, transforming, decoding, hardware access or many others.<br />
* Data passes between blocks in various formats, complex or real integers, floats or basically any kind of data type you can define.<br />
* Every flow graph needs at least one sink and source.<br />
<br />
= A first working code example =<br />
<br />
Next step is to find out how to write those flow graphs in real Python. Let's start by examining some code line-by-line. If you are familiar with Python, you can probably skip some of the explanations, but don't rush to the next section yet - the explanations are both for Python and GNU Radio beginners.<br />
<br />
The following code example represents the flow graph from example 2. It is actually a slightly modified version of the code example you can find in gr-audio/examples/python/dial_tone.py.<br />
<br />
<pre><br />
#!/usr/bin/env python3<br />
<br />
from gnuradio import gr<br />
from gnuradio import audio, analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, &quot;&quot;)<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass</pre><br />
The first line should look familiar to anyone with some Unix or Linux background: It tells the shell that this file is a Python file and to use the Python interpreter to run this file. You need this line if you want to run this file directly from the command line.<br />
<br />
Lines 3 and 4 import necessary Python modules to run GNU Radio. The <code>import</code> command is similar to the <code>#include</code> directive in C/C++. Here, three modules from the gnuradio package are imported: <code>gr</code>, <code>audio</code>, and <code>analog</code>. The first module, <code>gr</code>, is the basic GNU Radio module. You will always have to import this to run a GNU Radio application. The second loads [http://gnuradio.org/doc/doxygen/page_audio.html audio device blocks], and the third is where the blocks related to [http://gnuradio.org/doc/doxygen/page_analog.html ''analog'' signal functionality and modulation] are located. There are many GNU Radio modules, a short list of modules will be presented later on.<br />
<br />
Lines 6-17 define a class called <code>my_top_block</code> which is derived from another class, <code>gr.top_block</code>. This class is basically a container for the flow graph. By deriving from <code>gr.top_block</code>, you get all the hooks and functions you need to add blocks and connect them.<br />
<br />
Only one member function is defined for this class: the function <code>__init__()</code>, which is the constructor of this class. In the first line of this function (line 8), the parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br /><br />
Next, two variables are defined: <code>sample_rate</code> and <code>ampl</code>. These will control sampling rate and amplitude of the signal generators.<br />
<br />
Before explaining the next lines, have another look at the sketched flow graph chart in the previous section: it consists of three blocks and two edges. The blocks are defined in lines 13-15: Two [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html signal sources] are generated (called <code>src0</code> and <code>src1</code>). These sources continuously create sine waves at given frequencies (350 and 440Hz) and a given sampling rate (here 32kHz). The amplitude is controlled by the ampl variable and set to 0.1. The prefix &quot;f&quot; of the block type <code>analog.sig_source_f</code> indicates the output is of type <code>float</code>, which is a good thing because the audio sink accepts floating point samples in the range between 1 and +1. These kind of things must be taken care of by the programmer: although GNU Radio does some checks to make sure the connections make sense, there is still some things that must be taken care of manually. For example, if you wanted to feed integer samples to <code>audio.sink</code>, GNU Radio would throw an error but if you would set the amplitude in the above example to anything larger than 1, you would get a distorted signal without receiving an error.<br />
<br />
The signal sink is defined in line 15: <code>audio.sink()</code> returns a block which acts as a [http://gnuradio.org/doc/doxygen/classgr_1_1audio_1_1sink.html soundcard control] and plays back any samples piped into it. As in the blocks beforehand, the sampling rate needs to be set explicitly, even though this was set already for the signal sources. GNU Radio cannot guess the correct sampling rate from the context, as it is not part of the information flow between blocks.<br />
<br />
Lines 16 and 17 connect the blocks. The general syntax for connecting blocks is <code>self.connect(block1, block2, block3, ...)</code> which would connect the output of <code>block1</code> with the input of <code>block2</code>, the output of <code>block2</code> with the input of <code>block3</code> and so on. You can connect as many blocks as you wish with one <code>connect()</code> call. Here, a special syntax is necessary because we want to connect <code>src0</code> with the first input of <code>dst</code> and <code>src1</code> with the second one. <code>self.connect (src0, (dst, 0))</code> does exactly this: it specifically connects <code>src0</code> to port 0 of <code>dst</code>. <code>(dst, 0)</code> is called a &quot;tuple&quot; in Python jargon. In the <code>self.connect()</code> call it is used to specify the port number. When the port number is zero, the block may be used alone. An equivalent command to the one in line 16 would thus have been<br />
<br />
<pre><br />
self.connect((src0, 0), (dst, 0))</pre><br />
That's all there is to create a flow graph. The last 5 lines do nothing but start the flow graph (line 22). The <code>try</code> and <code>except</code> statements simply make sure the flow graph (which would otherwise run infinitely) are stopped when Ctrl+C is pressed (which triggers a <code>KeyboardInterrupt</code> Python exception).<br />
<br />
For Python-beginners, two more remarks should not be left out: As you might have noticed, the class my_top_block is run without creating an instance beforehand. In Python, this is a quite common thing to do, especially if you have a class which would only get one instance anyway. However, you could just as well create one or more instances of the class and then call the <code>run()</code> method on the instance(es).<br />
<br />
Second, the indenting is part of the code and not, like in C++, simply for the programmers convenience. If you try and modify this code, make sure you don't start mixing tabs and spaces. Every level must be consistently indented.<br /><br />
If you want to go on with this tutorial, you should first get a more solid Python background. Python documentation can be found at the Python web site http://www.python.org/, or a library of your choice. A good place to start for people with prior programming experience is http://wiki.python.org/moin/BeginnersGuide/Programmers .<br />
<br />
== Summary ==<br />
<br />
* You need to import required GNU Radio modules with the <code>from gnuradio import</code> command. You always need the module <code>gr</code>.<br />
* A flow graph is contained in a class which itself is derived from <code>gr.top_block</code>.<br />
* Blocks are created by calling functions such as <code>analog.sig_source_f()</code> and saving the return value to a variable.<br />
* Blocks are connected by calling <code>self.connect()</code> from within the flow graph class<br />
* If you don't feel comfortable writing some basic Python code now, have a break and go through some Python tutorials.<br />
<br />
The next section will give a more detailed overview about writing GNU Radio applications in Python.<br />
<br />
= Coding Python GNU Radio Applications =<br />
<br />
The example above already covers quite a lot of how to write Python GNU Radio applications. This chapter and the next will try to show the possibilities of GNU Radio applications and how to use them. From now on, there is no need to linearly read these chapters section-for-section, it probably makes more sense to go over the titles and find out what you want to know.<br />
<br />
== GNU Radio Modules ==<br />
<br />
GNU Radio comes with quite a lot of libraries and modules. You will usually include modules with the following syntax:<br />
<br />
<pre><br />
from gnuradio import MODULENAME</pre><br />
Some modules work a bit differently, see the following list on the most common modules.<br />
<br />
{|<br />
|<br />
<br />
| gr<br />
|<br />
<br />
| The main GNU Radio library. You will nearly always need this.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| analog<br />
|<br />
<br />
| Anything related to analog signals and analog modulation<br />
|<br />
<br />
|-<br />
|<br />
<br />
| audio<br />
|<br />
<br />
| Soundcard controls (sources, sinks). You can use this to send or receive audio to the sound cards, but you can also use your sound card as a narrow band receiver with an external RF frontend.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| blocks<br />
|<br />
<br />
| Everything else. If it doesn't fit in another category, it's probably here.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| channels<br />
|<br />
<br />
| Channel models for simulation.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| digital<br />
|<br />
<br />
| Anything related to digital modulation.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| fec<br />
|<br />
<br />
| Anything related to Forward Error Correction (FEC).<br />
|<br />
<br />
|-<br />
|<br />
<br />
| fft<br />
|<br />
<br />
| Anything related to FFTs.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| filter<br />
|<br />
<br />
| Filter blocks and design tools like [http://gnuradio.org/doc/doxygen/classgr_1_1filter_1_1firdes.html firdes] and optfir.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| plot_data<br />
|<br />
<br />
| Some functions to plot data with Matplotlib<br />
|<br />
<br />
|-<br />
|<br />
<br />
| qtgui<br />
|<br />
<br />
| Graphical tools to plot data (time, frequency, spectrogram, constellation, histogram, time raster) using the QT library.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| trellis<br />
|<br />
<br />
| Blocks and tools for building trellis, trellis coded modulation, and other FSMs.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| vocoder<br />
|<br />
<br />
| Anything dealing with voice coding/decoding.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| wavelet<br />
|<br />
<br />
| Anything dealing with wavelets.<br />
|<br />
<br />
<br />
|-<br />
|<br />
<br />
| eng_notation<br />
|<br />
<br />
| Adds some functions to deals with numbers in engineering notation such as @100M' for 100 * 10^6'.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| eng_options<br />
|<br />
<br />
| Use <code>from gnuradio.eng_options import eng_options</code> to import this feature. This module extends Pythons <code>optparse</code> module to understand engineering notation (see above).<br />
|<br />
<br />
|-<br />
|<br />
<br />
| gru<br />
|<br />
<br />
| Miscellaneous utilities, mathematical and others.<br />
|<br />
<br />
|}<br />
<br />
This is by far not a complete list, nor are the descriptions of the modules very useful by themselves. GNU Radio code changes a lot, so creating a static documentation would not be very sensible. GNU Radio uses [http://gnuradio.org/doc/doxygen/index.html Doxygen] and [http://gnuradio.org/doc/sphinx/index.html Sphinx] to dynamically create documentation of the APIs.<br />
<br />
Instead, you will have to use the good old Star Wars motto to delve further into the details of the modules: &quot;Use the source!&quot;. If you feel GNU Radio should really already have some functionality you want to use, either browse through the module directory Python uses or go through the source directory of GNU Radio. In particular, pay attention to the directories starting with <code>gr-</code> in the source directory, such as gr-trellis. These produce their own code and, consequently, their own modules.<br />
<br />
Of course, Python itself comes with a lot of modules, some of which are extremely useful - if not necessary - to write GNU Radio applications. Check the Python documentation and the SciPy website for more information.<br />
<br />
== Choosing, defining and configuring blocks ==<br />
<br />
GNU Radio comes with an abundance of pre-defined blocks, so for beginners, it is often quite confusing to find the correct blocks for their applications and set them up correctly. Doxygen and Sphinx are used to automatically generate documentation for the C++ and Python APIs ([http://gnuradio.org/doc/doxygen/index.html C++ Manual], [http://gnuradio.org/doc/sphinx/index.html) Python Manual].<br />
<br />
You can also generate this documentation locally so it always matches the version you have installed. If you have Doxygen and Sphinx installed, CMake takes care of this automatically.<br />
<br />
Learning how to use these documentations is a major part of learning how to use GNU Radio!<br />
<br />
Let's get practical. Here's the three lines from the previous example which define the blocks:<br />
<br />
<pre><br />
src0 = analog.sig_source_f (sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f (sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink (sample_rate, &quot;&quot;)</pre><br />
Here's a simplified version of what happens when this code is executed: First, a function called <code>sig_source_f</code> in the module <code>analog</code> is executed. It receives four function arguments:<br />
<br />
* sample_rate, which is a Python variable,<br />
* analog.GR_SIN_WAVE, which is a constant defined in the <code>analog</code> module (defined [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177) here],<br />
* 350, a normal literal constant that's the frequency of the sine wave (relative to the sample rate),<br />
* ampl, another variable to set the amplitude of the sine wave.<br />
<br />
This function creates a class which is subsequently assigned to <code>src0</code>. The same happens on the other two lines, although the sink is fetched from a different module (<code>audio</code>).<br />
<br />
So how did I know which block to use and what to pass to <code>analog.sig_source_f()</code>? This is where the documentation comes in. If you use the Sphinx-generated docs, click on &quot;gnuradio.analog&quot;. Proceed to &quot;Signal Sources&quot;. You will find a list of signal generators, including the <code>sig_source_*</code> family. The suffix defines the data type at the output:<br />
<br />
* f = float<br />
* c = complex float<br />
* i = int<br />
* s = short int<br />
* b = bits (actually an integer type)<br />
<br />
These suffixes are used for all types of blocks, e.g. <code>filter.fir_filter_ccf()</code> will define an FIR filter with complex input, complex output and float taps, and <code>blocks.add_const_ss()</code> will define a block which adds incoming short values with another, constant, short int.<br />
<br />
Even if you don't want to touch C+'', it's worth having a look at the Doxygen-generated documentation as well, since most of the blocks are actually written in C''+ but then exported to Python.<br />
<br />
At this point, it is worth having a closer look behind the curtains of GNU Radio. The reason you can easily use the blocks - written in C++ - in your Python code is because GNU Radio uses a tool called [http://www.swig.org/ SWIG] to create an interface between Python and C+''. Every block in C''+ comes with a creating function, called <code>gr::component::block::make(***)</code> (<code>gr::analog::sig_source_f::make()</code> in the example mentioned above). This function is always documented on the same page as the matching class, and this function is what gets exported to Python, so <code>analog.sig_source_f()</code> in Python calls <code>gr::analog::sig_source_f::make()</code> in C++. For the same reason, it takes the same arguments - that's how you know how to initialize a block in Python.<br />
<br />
Once you're browsing the Doxygen documentation of the class <code>gr::analog::sig_source_f</code>, you might notice many other class methods, such as <code>set_frequency()</code>. These functions get exported to Python as well. So if you have created a signal source and want to change the frequency (say your application has a user frequency control) you can use this method on your Python defined block:<br />
<br />
<pre><br />
# We're in some cool application here<br />
<br />
src0 = analog.sig_source_f (sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
# Other, fantastic things happen here<br />
<br />
src0.set_frequency(880) # Change frequency</pre><br />
will change the frequency of the first signal generator to 880Hz.<br />
<br />
Hopefully, GNU Radio documentation will grow and become more and more complete. But to completely understand the workings of blocks in detail, you will probably have to have a look at the code sooner or later, no matter how good the documentation gets.<br />
<br />
== Connecting blocks ==<br />
<br />
Use the <code>connect()</code> method of <code>gr.top_block</code> to connect blocks. Some things are worth mentioning:<br />
<br />
* You can only connect inputs and outputs if the data types match. If you try to connect a float output with a complex input, you will get an error.<br />
* One output can be connected to several inputs; you don't need an extra block to duplicate signal paths.<br />
<br />
These are basic rules for connecting blocks and they work in most cases. However, when mixing data types some more notes are worth mentioning.<br />
<br />
* GNU Radio checks if input and output types match by checking their size. If you happen to connect up ports with different types but the same size, you will most definitely get data junk.<br />
* When processing single bits, be careful. In some cases, you will work with binary data in a usual sense, in other cases you want to handle a specific number of bits at a time. Have a look at the <code>packed_to_unpacked*</code> and <code>unpacked_to_packed*</code> blocks for this.<br />
* Be careful with dynamic ranges. When you're using float or complex data types, you have a larger range than you'll ever need concerning the machine, but some sinks and sources have specific ranges you need to stick to. For example, audio sinks require samples within ''-1 and will clip anything outside this interval. The USRP sink on the other hand needs samples in the''-32767 range (signed 16 bit values) because that's the dynamic range of the DAC.<br />
<br />
== Hierarchical blocks ==<br />
<br />
Sometimes it makes sense to combine several blocks into a new block. Say you have several applications which all have a common signal processing component which consists of several blocks. These blocks can be combined into a new block, which in turn can be used in your applications is if it were a normal GNU Radio block.<br />
<br />
Example: Say you have two different flow graphs, FG1 and FG2. Both use - among others - the blocks B1 and B2. You want to combine them to a hierarchical block called <code>HierBlock</code>:<br />
<br />
<pre> +-------------------+<br />
| +-----+ +----+ |<br />
--+--+ B1 +--+ B2 +--+---<br />
| +-----+ +----+ |<br />
| HierBlock |<br />
+-------------------+</pre><br />
This is what you do: create a flow graph which derives from <code>gr.hier_block2</code> and use <code>self</code> as source and sink:<br />
<br />
<pre><br />
class HierBlock(gr.hier_block2):<br />
def __init__(self, audio_rate, if_rate):<br />
gr.hier_block2.__init__(self, &quot;HierBlock&quot;,<br />
gr.io_signature(1, 1, gr.sizeof_float),<br />
gr.io_signature(1, 2, gr.sizeof_gr_complex))<br />
<br />
B1 = blocks.block1(...) # Put in proper code here!<br />
B2 = blocks.block2(...)<br />
<br />
self.connect(self, B1, B2, self)</pre><br />
As you can see, creating a hierarchical block is very similar to creating a flow graph with <code>gr.top_block</code>. Apart from using <code>self</code> as source and sink, there is another difference: the constructor for the parent class (called in line 3) needs to receive additional information. The call to <code>gr.hier_block2.+init+()</code> takes four parameters:<br />
<br />
* self (which is always passed to the constructor as first argument),<br />
* a string with an identifier for the hierarchical block (change at your convenience),<br />
* an input signature and an<br />
* output signature.<br />
<br />
The last two require some extra explanation unless you have already written your own blocks in C++. GNU Radio needs to know what types of input and output the block uses. Creating an input/output signature can be done by calling <code>gr.io_signature()</code>, as is done here. This function call takes 3 arguments:<br />
<br />
* minimum number of ports,<br />
* maximum number of ports and<br />
* size of the input/output elements.<br />
<br />
For the hierarchical block <code>HierBlock</code>, you can see that it has exactly one input and one or two outputs. The incoming objects are of size <code>float</code>, so the block processes incoming real float values. Somewhere in B1 or B2, the data is converted to complex float values, so the output signature declares outgoing objects to be of size <code>gr.sizeof_gr_complex</code>. The 'gr.sizeof_float@ and <code>gr.sizeof_gr_complex</code> are equivalent to the C++ return values of the <code>sizeof()</code> call. Other predefined constants are<br />
<br />
* gr.sizeof_int<br />
* gr.sizeof_short<br />
* gr.sizeof_char<br />
<br />
Use gr.io_signature(0, 0, 0) to create a null IO signature, i.e. for defining hierarchical blocks as sources or sinks.<br />
<br />
That's all. You can now use <code>HierBlock</code> as you would use a regular block. For example, you could put this code in the same file:<br />
<br />
<pre><br />
class FG1(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
<br />
... # Sources and other blocks are defined here<br />
other_block1 = blocks.other_block()<br />
hierblock = HierBlock()<br />
other_block2 = blocks.other_block()<br />
<br />
self.connect(other_block1, hierblock, other_block2)<br />
<br />
... # Define rest of FG1</pre><br />
Of course, to make use of Pythons modularity, you could also put the code for <code>HierBlock</code> in an extra file called <code>hier_block.py</code>. To use this block from another file, simply add an import directive to your code:<br />
<br />
<pre><br />
from hier_block import HierBlock</pre><br />
and you can use <code>HierBlock</code> as mentioned above.<br />
<br />
Examples for hierarchical blocks:<br />
<br />
<pre>gr-uhd/examples/python/fm_tx_2_daughterboards.py<br />
gr-uhd/examples/python/fm_tx4.py<br />
gr-digital/examples/narrowband/tx_voice.py</pre><br />
== Multiple flow graphs ==<br />
<br />
In some cases, you might want to have completely separate flow graphs, e.g. for receive and transmit paths (see the example 'Walkie-Talkie' above). Currently (June 2008), it is not possible to have multiple top_blocks running at the same time, but what you can do is create full flow graphs as hierarchical blocks using <code>gr.hier_block2</code> like in the section above. Then, create a top_block to hold the flow graphs.<br />
<br />
Example:<br />
<br />
<pre><br />
class transmit_path(gr.hier_block2):<br />
def __init__(self):<br />
gr.hier_block2.__init__(self, &quot;transmit_path&quot;,<br />
gr.io_signature(0, 0, 0), # Null signature<br />
gr.io_signature(0, 0, 0))<br />
<br />
source_block = blocks.source()<br />
signal_proc = blocks.other_block()<br />
sink_block = blocks.sink()<br />
<br />
self.connect(source_block, signal_proc, sink_block)<br />
<br />
<br />
class receive_path(gr.hier_block2):<br />
def __init__(self):<br />
gr.hier_block2.__init__(self, &quot;receive_path&quot;,<br />
gr.io_signature(0, 0, 0), # Null signature<br />
gr.io_signature(0, 0, 0))<br />
<br />
source_block = blocks.source()<br />
signal_proc = blocks.other_block()<br />
sink_block = blocks.sink()<br />
<br />
self.connect(source_block, signal_proc, sink_block)<br />
<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
tx_path = transmit_path()<br />
<br />
rx_path = receive_path()<br />
<br />
self.connect(tx_path)<br />
self.connect(rx_path)</pre><br />
Now, when you start <code>my_top_block</code>, both flow graphs are started in parallel. Note that the hierarchical blocks have explicitly no inputs and outputs defined, they have a null IO signature. Consequently, they don't connect to <code>self</code> as source or sink; they rather define their own sources or sink (just as you would do when defining a hierarchical block as source or sink). The top block simply connects the hierarchical blocks to itself, but does not connect them up in any way.<br />
<br />
Examples for multiple flow graphs:<br />
<br />
<pre>gr-uhd/examples/python/usrp_nbfm_ptt.py</pre><br />
== GNU Radio extensions and tools ==<br />
<br />
GNU Radio is more than blocks and flow graphs - it comes with a lot of tools and code to help you write DSP applications.<br />
<br />
A collection of useful GNU Radio applications designed to aid you is in gr-utils/.<br /><br />
Browse the source code in gnuradio-runtime/python/gnuradio (and other gr-<component>/python directories) to find utilities you can use in your Python code such as filter design code, modulation utilities, and more.<br />
<br />
== Controlling flow graphs ==<br />
<br />
If you have followed the tutorial so far, you will have noticed that a flow graph has always been implemented as a class, derived from <code>gr.top_block</code>. The question remains on how to control one of these classes.<br />
<br />
As mentioned before, deriving the class from gr.top_block brings along all the functionality you might need. To run or stop an existing flow graph, use the following methods:<br />
<br />
{|<br />
|<br />
<br />
| <code>run()</code><br />
|<br />
<br />
| The simplest way to run a flow graph. Calls start(), then wait(). Used to run a flow graph that will stop on its own, or to run a flow graph indefinitely until SIGINT is received.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>start()</code><br />
|<br />
<br />
| Start the contained flow graph. Returns to the caller once the threads are created.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>stop()</code><br />
|<br />
<br />
| Stop the running flow graph. Notifies each thread created by the scheduler to shutdown, then returns to caller.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>wait()</code><br />
|<br />
<br />
| Wait for a flow graph to complete. Flowgraphs complete when either (1) all blocks indicate that they are done, or (2) after stop has been called to request shutdown.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>lock()</code><br />
|<br />
<br />
| Lock a flow graph in preparation for reconfiguration.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>unlock()</code><br />
|<br />
<br />
| Unlock a flow graph in preparation for reconfiguration. When an equal number of calls to lock() and unlock() have occurred, the flow graph will be restarted automatically.<br />
|<br />
<br />
|}<br />
<br />
See the documentation for [http://gnuradio.org/doc/doxygen/classgr_1_1top__block.html <code>gr::top_block</code>] for more details.<br />
<br />
Example:<br />
<br />
<pre><br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
... # Define blocks etc. here<br />
<br />
if __name__ == '__main__':<br />
my_top_block().start()<br />
sleep(5) # Wait 5 secs (assuming sleep was imported!)<br />
my_top_block().stop()<br />
my_top_block().wait() # If the graph is needed to run again, wait() must be called after stop<br />
... # Reconfigure the graph or modify it<br />
my_top_block().start() # start it again<br />
sleep(5) # Wait 5 secs (assuming sleep was imported!)<br />
my_top_block().stop() # since (assuming) the graph will not run again, no need for wait() to be called</pre><br />
These methods help you to control the flow graph from the outside. For many problems this might not be enough: you don't simply want to start or stop a flow graph, you want to reconfigure the way it behaves. For example, imagine your application has a volume control somewhere in your flow graph. This volume control is implemented by inserting a multiplier into the sample stream. This multiplier is of type <code>blocks.multiply_const_ff</code>. If you check the documentation for this kind of of block, you will find a function <code>blocks.multiply_const_ff.set_k()</code> which sets the multiplication factor.<br /><br />
You need to make the settings visible to the outside in order to control it. The simplest way is to make the block an attribute of the flow graph class.<br />
<br />
Example:<br />
<br />
<pre><br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
... # Define some blocks<br />
self.amp = blocks.multiply_const_ff(1) # Define multiplier block<br />
... # Define more blocks<br />
<br />
self.connect(..., self.amp, ...) # Connect all blocks<br />
<br />
def set_volume(self, volume):<br />
self.amp.set_k(volume)<br />
<br />
if __name__ == '__main__':<br />
my_top_block().start()<br />
sleep(2) # Wait 2 secs (assuming sleep was imported!)<br />
my_top_block.set_volume(2) # Pump up the volume (by factor 2)<br />
sleep(2) # Wait 2 secs (assuming sleep was imported!)<br />
my_top_block().stop()</pre><br />
This example runs the flow graph for 2 seconds and then doubles the volume by accessing the <code>amp</code> block through a member function called <code>set_volume()</code>. Of course, one could have accessed the <code>amp</code> attribute directly, omitting the member function.<br />
<br />
Hint: making blocks attributes of the flow graph is generally a good idea as it makes extending the flow graph with extra member functions easier.<br />
<br />
== Non-flow graph centered applications ==<br />
<br />
Up until now, GNU Radio applications in this tutorial have always been centered around the one class derived from <code>gr.top_block</code>. However, this is not necessarily how GNU Radio needs to be used. GNU Radio was designed to develop DSP applications from Python, so there's no reason to not use the full power of Python when using GNU Radio.<br />
<br />
Python is an extremely powerful language, and new libraries and functionalities are constantly being added. In a way, GNU Radio extends Python with a powerful, real-time-capable DSP library. By combining this with other libraries you have immense functionality right there at your fingertips. For example, by combining GNU Radio with SciPy, a collection of scientific Python libraries, you can record RF signals in real time and do extensive mathematical operations off line, save statistics to a database and so on - all in the same application. Even expensive engineering software such as Matlab might become unnecessary if you combine all these libraries.<br />
<br />
http://www.scipy.org/<br />
<br />
= Advanced Topics =<br />
<br />
If you have really read the previous sections, you already know enough to write your first Python GNU Radio applications. This section will address some slightly more advanced functionalities for Python GNU Radio applications.<br />
<br />
== Dynamic flow graph creation ==<br />
<br />
For most cases, the aforementioned way to define flow graphs is completely adequate. If you need more flexibility in your application, you might want to have even more control over the flow graph from outside the class.<br /><br />
This can be achieved by taking the code out of the <code>+init+()</code> function and simply using <code>gr.top_block</code> as a container. Example:<br />
<br />
<pre><br />
... # We are inside some application<br />
tb = gr.top_block() # Define the container<br />
<br />
block1 = blocks.some_other_block()<br />
block2 = blocks.yet_another_block()<br />
<br />
tb.connect(block1, block2)<br />
<br />
... # The application does some wonderful things here<br />
<br />
tb.start() # Start the flow graph<br />
<br />
... # Do some more incredible and fascinating stuff here</pre><br />
If you are writing some application which needs to dynamically stop a flow graph (reconfigure it, re-start it and so) on this might be a more practical way to do it.<br />
<br />
Examples for this kind of flow graph setup:<br />
<br />
<pre>gr-uhd/apps/hf_explorer/hfx.py</pre><br />
== Command Line Options ==<br />
<br />
Python has its own libraries to parse command line options. See the documentation for the module <code>optparse</code> to find out how to use it.<br />
<br />
GNU Radio extends optparse by new command line option types. Use @from gnuradio.eng_option import eng_option' to import this extension. With eng_option, you have the following types:<br />
<br />
{|<br />
|<br />
<br />
| eng_float<br />
|<br />
<br />
| Like the original float option, but also accepts engineering notation like 101.8M<br />
|<br />
<br />
|-<br />
|<br />
<br />
| subdev<br />
|<br />
<br />
| Only accepts valid subdevice descriptors such as A:0 (To specify a daughterboard on a USRP)<br />
|<br />
<br />
|-<br />
|<br />
<br />
| intx<br />
|<br />
<br />
| Only accepts integers<br />
|<br />
<br />
|}<br />
<br />
If your application supports command line options, it would be ever so nice if you could stick to the GNU Radio conventions for command line options. You can find these (along with more hints for developers) in README.hacking.<br />
<br />
Nearly every GNU Radio example uses this feature. Try dial_tone.py for an easy example.<br />
<br />
== Graphical User Interfaces ==<br />
<br />
If you are a Python expert and also have some experience in writing GUIs for Python (using whatever GUI toolkit you like), you might not even need this section. As mentioned before, GNU Radio merely extends Python with DSP routines - so if you like, just go ahead and write a GUI application, add a GNU Radio flow graph to it and define some interfaces to carry GNU Radio information to your application and vice versa. If you want to plot your data, you could use Matplotlib or Qwt.<br />
<br />
However, sometimes you simply want to write a quick GUI application without bothering with setting up widgets, defining all the menus etc. GNU Radio comes with some predefined classes to help you write graphical GNU Radio applications.<br />
<br />
These modules are based on wxWidgets (or to be precise, wxPython), a platform-independent GUI toolkit. You will need some background in wxPython - but don't worry, it is not that complicated and there are several tutorials available on the net. Check the wxPython website for documentation (http://www.wxpython.org/).<br />
<br />
To use the GNU Radio wxWidgets tools, you need to import some modules:<br />
<br />
<pre><br />
from gnuradio.wxgui import stdgui2, fftsink2, slider, form</pre><br />
Here, 4 components were imported from the gnuradio.wxgui submodule. Here's a quick list of the modules (again, not necessarily complete. You will have to browse the modules or the source code in gr-wxgui/python).<br />
<br />
{|<br />
|<br />
<br />
| stdgui2<br />
|<br />
<br />
| Basic GUI stuff, you always need this<br />
|<br />
<br />
|-<br />
|<br />
<br />
| fftsink2<br />
|<br />
<br />
| Plot FFTs of your data to create spectrum analyzers or whatever<br />
|<br />
<br />
|-<br />
|<br />
<br />
| scopesink2<br />
|<br />
<br />
| Oscilloscope output<br />
|<br />
<br />
|-<br />
|<br />
<br />
| waterfallsink2<br />
|<br />
<br />
| Waterfall output<br />
|<br />
<br />
|-<br />
|<br />
<br />
| numbersink2<br />
|<br />
<br />
| Displays numerical values of incoming data<br />
|<br />
<br />
|-<br />
|<br />
<br />
| form<br />
|<br />
<br />
| Often used input forms (see below)<br />
|<br />
<br />
|}<br />
<br />
Next, we have to define a new flow graph. This time, we don't derive from <code>gr.top_block</code> but from <code>stdgui2.std_top_block</code>:<br />
<br />
<pre><br />
class my_gui_flow_graph(stdgui2.std_top_block):<br />
def __init__(self, frame, panel, vbox, argv):<br />
stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)</pre><br />
As you can see, there's another difference: the constructor gets a couple of new parameters. This is because a <code>stdgui2.std_top_block</code> does not only include flow graph functionality (it is derived from gr.top_block itself), but also directly creates a window with some basic components (like a menu). This is good news for all of those who just want to quickly hack a graphical application: GNU Radio creates the window and everything, you just need to add the widgets. Here's a list of what you can do with these new objects (this probably won't mean much to you if you have no idea about GUI programming):<br />
<br />
{|<br />
|<br />
<br />
| frame<br />
|<br />
<br />
| The wx.Frame of your window. You can get at the predefined menu by using frame.GetMenuBar()<br />
|<br />
<br />
|-<br />
|<br />
<br />
| panel<br />
|<br />
<br />
| A panel, placed in @frame', to hold all your wxControl widgets<br />
|<br />
<br />
|-<br />
|<br />
<br />
| vbox<br />
|<br />
<br />
| A vertical box sizer (wx.BoxSizer(wx.VERTICAL) is how it is defined), used to align your widgets in the panel<br />
|<br />
<br />
|-<br />
|<br />
<br />
| argv<br />
|<br />
<br />
| The command line arguments<br />
|<br />
<br />
|}<br />
<br />
Now you have all you need to create your GUI. You can simply add new box sizers and widgets to vbox, change the menu or whatever. Some typical functions have been simplified further in the GNU Radio GUI library <code>form</code>.<br />
<br />
form has a great number of input widgets: <code>form.static_text_field()</code> for static text field (display only), <code>form.float_field()</code>, to input float values, <code>form.text_field()</code> to input text, <code>form.checkbox_field()</code> for checkboxes, <code>form.radiobox_field()</code> for radioboxes etc. Check the source code of gr-wxgui/python/form.py for the complete list. Most of these calls pass most of their arguments to the appropriate wxPython objects, so the function arguments are quite self-explanatory.<br /><br />
See one of the examples mentioned below on how to add widgets using form.<br />
<br />
Probably the most useful part of <code>gnuradio.wxgui</code> is the possibility to directly plot incoming data. To do this, you need one of the sinks that come with <code>gnuradio.wxgui</code>, such as <code>fftsink2</code>. These sinks work just as any other GNU Radio sink, but also have properties needed for use with wxPython. Example:<br />
<br />
<pre><br />
from gnuradio.wxgui import stdgui2, fftsink2<br />
<br />
<br />
# App gets defined here ...<br />
<br />
# FFT display (pseudo-spectrum analyzer)<br />
my_fft = fftsink2.fft_sink_f(panel, title=&quot;FFT of some Signal&quot;, fft_size=512,<br />
sample_rate=sample_rate, ref_level=0, y_per_div=20)<br />
self.connect(source_block, my_fft)<br />
vbox.Add(my_fft.win, 1, wx.EXPAND)</pre><br />
First, the block is defined (<code>fftsink2.fft_sink_f</code>). Apart from typical DSP parameters such as the sampling rate, it also needs the <code>panel</code> object which is passed to the constructor. Next, the block is connected to a source. Finally, the FFT window (<code>my_fft.win</code>) is placed inside the <code>vbox</code> BoxSizer to actually display it. Remember that a signal block output can be connected to any amount of inputs.<br />
<br />
Finally, the whole thing needs to be started. Because we need an <code>wx.App()</code> to run the GUI, the start-up code is a bit different from a regular flow graph:<br />
<br />
<pre><br />
if __name__ == '__main__':<br />
app = stdgui2.stdapp(my_gui_flow_graph, &quot;GUI GNU Radio Application&quot;)<br />
app.MainLoop()</pre><br />
<code>stdgui2.stdapp()</code> creates the <code>wx.App</code> with <code>my_gui_flow_graph</code> (the first argument). The window title is set to &quot;GUI GNU Radio Application&quot;.<br />
<br />
Examples for simple GNU Radio GUIs:<br />
<br />
<pre>gr-uhd/apps/uhd_fft<br />
gr-audio/examples/python/audio_fft.py<br />
./gr-uhd/examples/python/usrp_am_mw_rcv.py</pre><br />
And many more.<br />
<br />
= What next? =<br />
<br />
Young Padawan, no more there is I can teach you. If you have any more questions on how to write GNU Radio applications in Python, there are still a number of resources you can use:<br />
<br />
* Use the source. Especially the examples in gr-<component>/examples/ and gr-utils/ can be very helpful.<br />
* Check the [http://gnuradio.org/redmine/projects/gnuradio/wiki/MailingLists mailing list] archives. Chances are very high your problem has been asked before.<br />
<br />
[[Category:Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=TutorialsWritePythonApplications&diff=6378TutorialsWritePythonApplications2019-11-24T19:33:22Z<p>Cmrincon: update to version 3.8.0.0</p>
<hr />
<div>= Introduction =<br />
<br />
Welcome, GNU Radio beginners. If you are reading this tutorial, you probably already have some very basic knowledge about how GNU Radio works, what it is and what it can do - and now you want to enter this exciting world of Open Source digital signal processing (DSP) yourself.<br />
<br />
This is a tutorial on how to write applications for GNU Radio in Python using the version 3.7 API. It is no introduction to programming, software radio or signal processing, nor does it cover how to extend GNU Radio by creating new blocks or adding code to the source tree. If you have some background in the mentioned topics and are starting to work with GNU Radio, this probably is the correct tutorial for you. If you don't know what a Software Radio is or what a FIR filter does, you should probably go a few steps back and get a more solid background on signal processing theory. But don't let this discourage you - the best way to learn something is by trying it out.<br />
<br />
Although this tutorial is designed to make your introduction to GNU Radio as easy as possible, it is not a definitive guide. In fact, I might sometimes simply not tell the real truth to make explanations easier. I might even contradict myself in later chapters. Usage of brain power is still necessary to develop GNU Radio applications.<br />
<br />
= Preliminaries =<br />
<br />
Before you get started with this tutorial, make sure your GNU Radio installation is ready and working. You don't necessarily need a USRP, but some kind of source and sink (USRP, audio or other hardware) is helpful, although not strictly required. If the GNU Radio examples work (such as dial_tone.py in gr-audio/examples/python), you're ready to go.<br />
<br />
You should also have some background in programming - but don't worry if you've never programmed Python, it is a very easy language to learn.<br />
<br />
= Understanding flow graphs =<br />
<br />
Before we start banging out code, first we need to understand the most basic concepts about GNU Radio: flow graphs (as in graph theory) and blocks. Many GNU Radio applications contain nothing other than a flow graph. The nodes of such a graph are called blocks, and the data flows along the edges.<br />
<br />
Any actual signal processing is done in the blocks. Ideally, every block does exactly one job - this way GNU Radio stays modular and flexible. Blocks are written in C++; writing new blocks is not very difficult (but explained elsewhere).<br />
<br />
The data passing between blocks can be of any kind - practically any type of data you can define in C++ is possible. In practice, the most common data types are complex and real short or long integers and floating point values as most of the time, data passing from one block to the next will either be samples or bits.<br />
<br />
== Examples ==<br />
<br />
In order to illuminate this diffuse topic a little, let's start with some examples:<br />
<br />
'''Low-pass filtered audio recorder'''<br />
<br />
<pre> +-----+ +-----+ +----------------+<br />
| Mic +--+ LPF +--+ Record to file |<br />
+-----+ +-----+ +----------------+</pre><br />
First, an audio signal from a microphone is recorded by your PCs sound card and converted into a digital signal. The samples are streamed to the next block, the low pass filter (LPF), which could be implemented as an FIR filter. The filtered signal is passed on to the final block, which records the filtered audio signal into a file.<br />
<br />
This is a simple, yet complete flow graph. The first and last block serve a special purpose: they operate as source and sink. Every flow graph needs at least one source and sink to be able to function.<br />
<br />
'''Dial tone generator'''<br />
<br />
<pre> +------------------------+<br />
| Sine generator (350Hz) +---+<br />
+------------------------+ | +------------+<br />
+---+ |<br />
| Audio sink |<br />
+---+ |<br />
+------------------------+ | +------------+<br />
| Sine generator (440Hz) +---+<br />
+------------------------+</pre><br />
This simple example is often called the &quot;Hello World of GNU Radio&quot;. Other than the first example, it has two sources. The sink, on the other hand, has two inputs - in this case for the left and right channel of the sound card. Code for this example is available at gr-audio/examples/python/dial_tone.py.<br />
<br />
'''QPSK Demodulator'''<br />
<br />
<pre> +-------------+ +----------------+ +------------------+<br />
| USRP Source +--+ Frequency sync +--+ Matched filter |<br />
+-------------+ +----------------+ +-----------+------+<br />
| COMPLEX SAMPLES<br />
+-------------+------+<br />
| Symbol demodulator |<br />
+-------------+------+<br />
| COMPLEX SYMBOLS<br />
+-----------------+ +-----------------+ +------+------+<br />
| Source decoder +--+ Channel decoder +--+ Bit mapping |<br />
+--------+--------+ +-----------------+ +-------------+<br />
| BITS<br />
+--------+--------+<br />
| Application | DATA<br />
+-----------------+</pre><br />
This example is a bit more sophisticated, but should look quite familiar to RF engineers. In this case, the source is a USRP which is connected to an antenna. This kind of source sends complex samples to the following blocks.<br />
<br />
The interesting part about this kind of flow graph is that the data types change during the flow graph: at first, complex baseband samples are passed along. Then, complex symbols are gathered from the signal. Next, these symbols are turned into bits which again are processed further. Finally, the decoded bits are passed to some application which makes use of the data.<br />
<br />
'''Walkie Talkie'''<br />
<br />
<pre> +--------------+ +------------------+ +---------+ +------------+<br />
| USRP Source +--+ NBFM Demodulator +--+ Squelch +--+ Audio Sink |<br />
+--------------+ +------------------+ +---------+ +------------+<br />
+--------------+ +----------------+ +------------+<br />
| Audio Source +----------+ NBFM Modulator +---------+ USRP Sink |<br />
+--------------+ +----------------+ +------------+</pre><br />
This applications consists of two separate flow graphs, both running in parallel. One of them deals with the Tx path, the other with the Rx path. This kind of application would require some extra code (outside the flow graphs) to mute one path while the other is active. Both flow graphs still require at least one source and sink, each. You can find a GNU Radio application that does this (only a bit more sophisticated) at gr-uhd/examples/python/usrp_nbfm_ptt.py.<br />
<br />
== Summary ==<br />
<br />
This concludes the chapter about flow graphs. Here's a quick summary about the most vital points you really need to know:<br />
<br />
* All signal processing in GNU Radio is done through flow graphs.<br />
* A flow graph consists of blocks. A block does one signal processing operation, such as filtering, adding signals, transforming, decoding, hardware access or many others.<br />
* Data passes between blocks in various formats, complex or real integers, floats or basically any kind of data type you can define.<br />
* Every flow graph needs at least one sink and source.<br />
<br />
= A first working code example =<br />
<br />
Next step is to find out how to write those flow graphs in real Python. Let's start by examining some code line-by-line. If you are familiar with Python, you can probably skip some of the explanations, but don't rush to the next section yet - the explanations are both for Python and GNU Radio beginners.<br />
<br />
The following code example represents the flow graph from example 2. It is actually a slightly modified version of the code example you can find in gr-audio/examples/python/dial_tone.py.<br />
<br />
<pre><br />
#!/usr/bin/env python3<br />
<br />
from gnuradio import gr<br />
from gnuradio import audio, analog<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
sample_rate = 32000<br />
ampl = 0.1<br />
<br />
src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink(sample_rate, &quot;&quot;)<br />
self.connect(src0, (dst, 0))<br />
self.connect(src1, (dst, 1))<br />
<br />
<br />
if __name__ == '__main__':<br />
try:<br />
my_top_block().run()<br />
except [[KeyboardInterrupt]]:<br />
pass</pre><br />
The first line should look familiar to anyone with some Unix or Linux background: It tells the shell that this file is a Python file and to use the Python interpreter to run this file. You need this line if you want to run this file directly from the command line.<br />
<br />
Lines 3 and 4 import necessary Python modules to run GNU Radio. The <code>import</code> command is similar to the <code>#include</code> directive in C/C++. Here, three modules from the gnuradio package are imported: <code>gr</code>, <code>audio</code>, and <code>analog</code>. The first module, <code>gr</code>, is the basic GNU Radio module. You will always have to import this to run a GNU Radio application. The second loads [http://gnuradio.org/doc/doxygen/page_audio.html audio device blocks], and the third is where the blocks related to [http://gnuradio.org/doc/doxygen/page_analog.html ''analog'' signal functionality and modulation] are located. There are many GNU Radio modules, a short list of modules will be presented later on.<br />
<br />
Lines 6-17 define a class called <code>my_top_block</code> which is derived from another class, <code>gr.top_block</code>. This class is basically a container for the flow graph. By deriving from <code>gr.top_block</code>, you get all the hooks and functions you need to add blocks and connect them.<br />
<br />
Only one member function is defined for this class: the function <code>__init__()</code>, which is the constructor of this class. In the first line of this function (line 8), the parent constructor is called (in Python, this needs to be done explicitly. Most things in Python need to be done explicitly; in fact, this is one main Python principle).<br /><br />
Next, two variables are defined: <code>sample_rate</code> and <code>ampl</code>. These will control sampling rate and amplitude of the signal generators.<br />
<br />
Before explaining the next lines, have another look at the sketched flow graph chart in the previous section: it consists of three blocks and two edges. The blocks are defined in lines 13-15: Two [http://gnuradio.org/doc/doxygen/classgr_1_1analog_1_1sig__source__f.html signal sources] are generated (called <code>src0</code> and <code>src1</code>). These sources continuously create sine waves at given frequencies (350 and 440Hz) and a given sampling rate (here 32kHz). The amplitude is controlled by the ampl variable and set to 0.1. The prefix &quot;f&quot; of the block type <code>analog.sig_source_f</code> indicates the output is of type <code>float</code>, which is a good thing because the audio sink accepts floating point samples in the range between 1 and +1. These kind of things must be taken care of by the programmer: although GNU Radio does some checks to make sure the connections make sense, there is still some things that must be taken care of manually. For example, if you wanted to feed integer samples to <code>audio.sink</code>, GNU Radio would throw an error but if you would set the amplitude in the above example to anything larger than 1, you would get a distorted signal without receiving an error.<br />
<br />
The signal sink is defined in line 15: <code>audio.sink()</code> returns a block which acts as a [http://gnuradio.org/doc/doxygen/classgr_1_1audio_1_1sink.html soundcard control] and plays back any samples piped into it. As in the blocks beforehand, the sampling rate needs to be set explicitly, even though this was set already for the signal sources. GNU Radio cannot guess the correct sampling rate from the context, as it is not part of the information flow between blocks.<br />
<br />
Lines 16 and 17 connect the blocks. The general syntax for connecting blocks is <code>self.connect(block1, block2, block3, ...)</code> which would connect the output of <code>block1</code> with the input of <code>block2</code>, the output of <code>block2</code> with the input of <code>block3</code> and so on. You can connect as many blocks as you wish with one <code>connect()</code> call. Here, a special syntax is necessary because we want to connect <code>src0</code> with the first input of <code>dst</code> and <code>src1</code> with the second one. <code>self.connect (src0, (dst, 0))</code> does exactly this: it specifically connects <code>src0</code> to port 0 of <code>dst</code>. <code>(dst, 0)</code> is called a &quot;tuple&quot; in Python jargon. In the <code>self.connect()</code> call it is used to specify the port number. When the port number is zero, the block may be used alone. An equivalent command to the one in line 16 would thus have been<br />
<br />
<pre><br />
self.connect((src0, 0), (dst, 0))</pre><br />
That's all there is to create a flow graph. The last 5 lines do nothing but start the flow graph (line 22). The <code>try</code> and <code>except</code> statements simply make sure the flow graph (which would otherwise run infinitely) are stopped when Ctrl+C is pressed (which triggers a <code>KeyboardInterrupt</code> Python exception).<br />
<br />
For Python-beginners, two more remarks should not be left out: As you might have noticed, the class my_top_block is run without creating an instance beforehand. In Python, this is a quite common thing to do, especially if you have a class which would only get one instance anyway. However, you could just as well create one or more instances of the class and then call the <code>run()</code> method on the instance(es).<br />
<br />
Second, the indenting is part of the code and not, like in C++, simply for the programmers convenience. If you try and modify this code, make sure you don't start mixing tabs and spaces. Every level must be consistently indented.<br /><br />
If you want to go on with this tutorial, you should first get a more solid Python background. Python documentation can be found at the Python web site http://www.python.org/, or a library of your choice. A good place to start for people with prior programming experience is http://wiki.python.org/moin/BeginnersGuide/Programmers .<br />
<br />
== Summary ==<br />
<br />
* You need to import required GNU Radio modules with the <code>from gnuradio import</code> command. You always need the module <code>gr</code>.<br />
* A flow graph is contained in a class which itself is derived from <code>gr.top_block</code>.<br />
* Blocks are created by calling functions such as <code>analog.sig_source_f()</code> and saving the return value to a variable.<br />
* Blocks are connected by calling <code>self.connect()</code> from within the flow graph class<br />
* If you don't feel comfortable writing some basic Python code now, have a break and go through some Python tutorials.<br />
<br />
The next section will give a more detailed overview about writing GNU Radio applications in Python.<br />
<br />
= Coding Python GNU Radio Applications =<br />
<br />
The example above already covers quite a lot of how to write Python GNU Radio applications. This chapter and the next will try to show the possibilities of GNU Radio applications and how to use them. From now on, there is no need to linearly read these chapters section-for-section, it probably makes more sense to go over the titles and find out what you want to know.<br />
<br />
== GNU Radio Modules ==<br />
<br />
GNU Radio comes with quite a lot of libraries and modules. You will usually include modules with the following syntax:<br />
<br />
<pre><br />
from gnuradio import MODULENAME</pre><br />
Some modules work a bit differently, see the following list on the most common modules.<br />
<br />
{|<br />
|<br />
<br />
| gr<br />
|<br />
<br />
| The main GNU Radio library. You will nearly always need this.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| analog<br />
|<br />
<br />
| Anything related to analog signals and analog modulation<br />
|<br />
<br />
|-<br />
|<br />
<br />
| audio<br />
|<br />
<br />
| Soundcard controls (sources, sinks). You can use this to send or receive audio to the sound cards, but you can also use your sound card as a narrow band receiver with an external RF frontend.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| blocks<br />
|<br />
<br />
| Everything else. If it doesn't fit in another category, it's probably here.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| channels<br />
|<br />
<br />
| Channel models for simulation.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| digital<br />
|<br />
<br />
| Anything related to digital modulation.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| fec<br />
|<br />
<br />
| Anything related to Forward Error Correction (FEC).<br />
|<br />
<br />
|-<br />
|<br />
<br />
| fft<br />
|<br />
<br />
| Anything related to FFTs.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| filter<br />
|<br />
<br />
| Filter blocks and design tools like [http://gnuradio.org/doc/doxygen/classgr_1_1filter_1_1firdes.html firdes] and optfir.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| plot_data<br />
|<br />
<br />
| Some functions to plot data with Matplotlib<br />
|<br />
<br />
|-<br />
|<br />
<br />
| qtgui<br />
|<br />
<br />
| Graphical tools to plot data (time, frequency, spectrogram, constellation, histogram, time raster) using the QT library.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| trellis<br />
|<br />
<br />
| Blocks and tools for building trellis, trellis coded modulation, and other FSMs.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| vocoder<br />
|<br />
<br />
| Anything dealing with voice coding/decoding.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| wavelet<br />
|<br />
<br />
| Anything dealing with wavelets.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| wxgui<br />
|<br />
<br />
| This is actually a submodule, containing utilities to quickly create graphical user interfaces to your flow graphs. Use <code>from gnuradio.wxgui import *</code> to import everything in the submodule or <code>from gnuradio.wxgui import stdgui2, fftsink2</code> to import specific components. See the section 'Graphical User Interfaces' for more information.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| eng_notation<br />
|<br />
<br />
| Adds some functions to deals with numbers in engineering notation such as @100M' for 100 * 10^6'.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| eng_options<br />
|<br />
<br />
| Use <code>from gnuradio.eng_options import eng_options</code> to import this feature. This module extends Pythons <code>optparse</code> module to understand engineering notation (see above).<br />
|<br />
<br />
|-<br />
|<br />
<br />
| gru<br />
|<br />
<br />
| Miscellaneous utilities, mathematical and others.<br />
|<br />
<br />
|}<br />
<br />
This is by far not a complete list, nor are the descriptions of the modules very useful by themselves. GNU Radio code changes a lot, so creating a static documentation would not be very sensible. GNU Radio uses [http://gnuradio.org/doc/doxygen/index.html Doxygen] and [http://gnuradio.org/doc/sphinx/index.html Sphinx] to dynamically create documentation of the APIs.<br />
<br />
Instead, you will have to use the good old Star Wars motto to delve further into the details of the modules: &quot;Use the source!&quot;. If you feel GNU Radio should really already have some functionality you want to use, either browse through the module directory Python uses or go through the source directory of GNU Radio. In particular, pay attention to the directories starting with <code>gr-</code> in the source directory, such as gr-trellis. These produce their own code and, consequently, their own modules.<br />
<br />
Of course, Python itself comes with a lot of modules, some of which are extremely useful - if not necessary - to write GNU Radio applications. Check the Python documentation and the SciPy website for more information.<br />
<br />
== Choosing, defining and configuring blocks ==<br />
<br />
GNU Radio comes with an abundance of pre-defined blocks, so for beginners, it is often quite confusing to find the correct blocks for their applications and set them up correctly. Doxygen and Sphinx are used to automatically generate documentation for the C++ and Python APIs ([http://gnuradio.org/doc/doxygen/index.html C++ Manual], [http://gnuradio.org/doc/sphinx/index.html) Python Manual].<br />
<br />
You can also generate this documentation locally so it always matches the version you have installed. If you have Doxygen and Sphinx installed, CMake takes care of this automatically.<br />
<br />
Learning how to use these documentations is a major part of learning how to use GNU Radio!<br />
<br />
Let's get practical. Here's the three lines from the previous example which define the blocks:<br />
<br />
<pre><br />
src0 = analog.sig_source_f (sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
src1 = analog.sig_source_f (sample_rate, analog.GR_SIN_WAVE, 440, ampl)<br />
dst = audio.sink (sample_rate, &quot;&quot;)</pre><br />
Here's a simplified version of what happens when this code is executed: First, a function called <code>sig_source_f</code> in the module <code>analog</code> is executed. It receives four function arguments:<br />
<br />
* sample_rate, which is a Python variable,<br />
* analog.GR_SIN_WAVE, which is a constant defined in the <code>analog</code> module (defined [http://gnuradio.org/doc/doxygen/group__waveform__generators__blk.html#gac97c0f42ffb63f1265decceaaeab9177) here],<br />
* 350, a normal literal constant that's the frequency of the sine wave (relative to the sample rate),<br />
* ampl, another variable to set the amplitude of the sine wave.<br />
<br />
This function creates a class which is subsequently assigned to <code>src0</code>. The same happens on the other two lines, although the sink is fetched from a different module (<code>audio</code>).<br />
<br />
So how did I know which block to use and what to pass to <code>analog.sig_source_f()</code>? This is where the documentation comes in. If you use the Sphinx-generated docs, click on &quot;gnuradio.analog&quot;. Proceed to &quot;Signal Sources&quot;. You will find a list of signal generators, including the <code>sig_source_*</code> family. The suffix defines the data type at the output:<br />
<br />
* f = float<br />
* c = complex float<br />
* i = int<br />
* s = short int<br />
* b = bits (actually an integer type)<br />
<br />
These suffixes are used for all types of blocks, e.g. <code>filter.fir_filter_ccf()</code> will define an FIR filter with complex input, complex output and float taps, and <code>blocks.add_const_ss()</code> will define a block which adds incoming short values with another, constant, short int.<br />
<br />
Even if you don't want to touch C+'', it's worth having a look at the Doxygen-generated documentation as well, since most of the blocks are actually written in C''+ but then exported to Python.<br />
<br />
At this point, it is worth having a closer look behind the curtains of GNU Radio. The reason you can easily use the blocks - written in C++ - in your Python code is because GNU Radio uses a tool called [http://www.swig.org/ SWIG] to create an interface between Python and C+''. Every block in C''+ comes with a creating function, called <code>gr::component::block::make(***)</code> (<code>gr::analog::sig_source_f::make()</code> in the example mentioned above). This function is always documented on the same page as the matching class, and this function is what gets exported to Python, so <code>analog.sig_source_f()</code> in Python calls <code>gr::analog::sig_source_f::make()</code> in C++. For the same reason, it takes the same arguments - that's how you know how to initialize a block in Python.<br />
<br />
Once you're browsing the Doxygen documentation of the class <code>gr::analog::sig_source_f</code>, you might notice many other class methods, such as <code>set_frequency()</code>. These functions get exported to Python as well. So if you have created a signal source and want to change the frequency (say your application has a user frequency control) you can use this method on your Python defined block:<br />
<br />
<pre><br />
# We're in some cool application here<br />
<br />
src0 = analog.sig_source_f (sample_rate, analog.GR_SIN_WAVE, 350, ampl)<br />
# Other, fantastic things happen here<br />
<br />
src0.set_frequency(880) # Change frequency</pre><br />
will change the frequency of the first signal generator to 880Hz.<br />
<br />
Hopefully, GNU Radio documentation will grow and become more and more complete. But to completely understand the workings of blocks in detail, you will probably have to have a look at the code sooner or later, no matter how good the documentation gets.<br />
<br />
== Connecting blocks ==<br />
<br />
Use the <code>connect()</code> method of <code>gr.top_block</code> to connect blocks. Some things are worth mentioning:<br />
<br />
* You can only connect inputs and outputs if the data types match. If you try to connect a float output with a complex input, you will get an error.<br />
* One output can be connected to several inputs; you don't need an extra block to duplicate signal paths.<br />
<br />
These are basic rules for connecting blocks and they work in most cases. However, when mixing data types some more notes are worth mentioning.<br />
<br />
* GNU Radio checks if input and output types match by checking their size. If you happen to connect up ports with different types but the same size, you will most definitely get data junk.<br />
* When processing single bits, be careful. In some cases, you will work with binary data in a usual sense, in other cases you want to handle a specific number of bits at a time. Have a look at the <code>packed_to_unpacked*</code> and <code>unpacked_to_packed*</code> blocks for this.<br />
* Be careful with dynamic ranges. When you're using float or complex data types, you have a larger range than you'll ever need concerning the machine, but some sinks and sources have specific ranges you need to stick to. For example, audio sinks require samples within ''-1 and will clip anything outside this interval. The USRP sink on the other hand needs samples in the''-32767 range (signed 16 bit values) because that's the dynamic range of the DAC.<br />
<br />
== Hierarchical blocks ==<br />
<br />
Sometimes it makes sense to combine several blocks into a new block. Say you have several applications which all have a common signal processing component which consists of several blocks. These blocks can be combined into a new block, which in turn can be used in your applications is if it were a normal GNU Radio block.<br />
<br />
Example: Say you have two different flow graphs, FG1 and FG2. Both use - among others - the blocks B1 and B2. You want to combine them to a hierarchical block called <code>HierBlock</code>:<br />
<br />
<pre> +-------------------+<br />
| +-----+ +----+ |<br />
--+--+ B1 +--+ B2 +--+---<br />
| +-----+ +----+ |<br />
| HierBlock |<br />
+-------------------+</pre><br />
This is what you do: create a flow graph which derives from <code>gr.hier_block2</code> and use <code>self</code> as source and sink:<br />
<br />
<pre><br />
class HierBlock(gr.hier_block2):<br />
def __init__(self, audio_rate, if_rate):<br />
gr.hier_block2.__init__(self, &quot;HierBlock&quot;,<br />
gr.io_signature(1, 1, gr.sizeof_float),<br />
gr.io_signature(1, 2, gr.sizeof_gr_complex))<br />
<br />
B1 = blocks.block1(...) # Put in proper code here!<br />
B2 = blocks.block2(...)<br />
<br />
self.connect(self, B1, B2, self)</pre><br />
As you can see, creating a hierarchical block is very similar to creating a flow graph with <code>gr.top_block</code>. Apart from using <code>self</code> as source and sink, there is another difference: the constructor for the parent class (called in line 3) needs to receive additional information. The call to <code>gr.hier_block2.+init+()</code> takes four parameters:<br />
<br />
* self (which is always passed to the constructor as first argument),<br />
* a string with an identifier for the hierarchical block (change at your convenience),<br />
* an input signature and an<br />
* output signature.<br />
<br />
The last two require some extra explanation unless you have already written your own blocks in C++. GNU Radio needs to know what types of input and output the block uses. Creating an input/output signature can be done by calling <code>gr.io_signature()</code>, as is done here. This function call takes 3 arguments:<br />
<br />
* minimum number of ports,<br />
* maximum number of ports and<br />
* size of the input/output elements.<br />
<br />
For the hierarchical block <code>HierBlock</code>, you can see that it has exactly one input and one or two outputs. The incoming objects are of size <code>float</code>, so the block processes incoming real float values. Somewhere in B1 or B2, the data is converted to complex float values, so the output signature declares outgoing objects to be of size <code>gr.sizeof_gr_complex</code>. The 'gr.sizeof_float@ and <code>gr.sizeof_gr_complex</code> are equivalent to the C++ return values of the <code>sizeof()</code> call. Other predefined constants are<br />
<br />
* gr.sizeof_int<br />
* gr.sizeof_short<br />
* gr.sizeof_char<br />
<br />
Use gr.io_signature(0, 0, 0) to create a null IO signature, i.e. for defining hierarchical blocks as sources or sinks.<br />
<br />
That's all. You can now use <code>HierBlock</code> as you would use a regular block. For example, you could put this code in the same file:<br />
<br />
<pre><br />
class FG1(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
<br />
... # Sources and other blocks are defined here<br />
other_block1 = blocks.other_block()<br />
hierblock = HierBlock()<br />
other_block2 = blocks.other_block()<br />
<br />
self.connect(other_block1, hierblock, other_block2)<br />
<br />
... # Define rest of FG1</pre><br />
Of course, to make use of Pythons modularity, you could also put the code for <code>HierBlock</code> in an extra file called <code>hier_block.py</code>. To use this block from another file, simply add an import directive to your code:<br />
<br />
<pre><br />
from hier_block import HierBlock</pre><br />
and you can use <code>HierBlock</code> as mentioned above.<br />
<br />
Examples for hierarchical blocks:<br />
<br />
<pre>gr-uhd/examples/python/fm_tx_2_daughterboards.py<br />
gr-uhd/examples/python/fm_tx4.py<br />
gr-digital/examples/narrowband/tx_voice.py</pre><br />
== Multiple flow graphs ==<br />
<br />
In some cases, you might want to have completely separate flow graphs, e.g. for receive and transmit paths (see the example 'Walkie-Talkie' above). Currently (June 2008), it is not possible to have multiple top_blocks running at the same time, but what you can do is create full flow graphs as hierarchical blocks using <code>gr.hier_block2</code> like in the section above. Then, create a top_block to hold the flow graphs.<br />
<br />
Example:<br />
<br />
<pre><br />
class transmit_path(gr.hier_block2):<br />
def __init__(self):<br />
gr.hier_block2.__init__(self, &quot;transmit_path&quot;,<br />
gr.io_signature(0, 0, 0), # Null signature<br />
gr.io_signature(0, 0, 0))<br />
<br />
source_block = blocks.source()<br />
signal_proc = blocks.other_block()<br />
sink_block = blocks.sink()<br />
<br />
self.connect(source_block, signal_proc, sink_block)<br />
<br />
<br />
class receive_path(gr.hier_block2):<br />
def __init__(self):<br />
gr.hier_block2.__init__(self, &quot;receive_path&quot;,<br />
gr.io_signature(0, 0, 0), # Null signature<br />
gr.io_signature(0, 0, 0))<br />
<br />
source_block = blocks.source()<br />
signal_proc = blocks.other_block()<br />
sink_block = blocks.sink()<br />
<br />
self.connect(source_block, signal_proc, sink_block)<br />
<br />
<br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
<br />
tx_path = transmit_path()<br />
<br />
rx_path = receive_path()<br />
<br />
self.connect(tx_path)<br />
self.connect(rx_path)</pre><br />
Now, when you start <code>my_top_block</code>, both flow graphs are started in parallel. Note that the hierarchical blocks have explicitly no inputs and outputs defined, they have a null IO signature. Consequently, they don't connect to <code>self</code> as source or sink; they rather define their own sources or sink (just as you would do when defining a hierarchical block as source or sink). The top block simply connects the hierarchical blocks to itself, but does not connect them up in any way.<br />
<br />
Examples for multiple flow graphs:<br />
<br />
<pre>gr-uhd/examples/python/usrp_nbfm_ptt.py</pre><br />
== GNU Radio extensions and tools ==<br />
<br />
GNU Radio is more than blocks and flow graphs - it comes with a lot of tools and code to help you write DSP applications.<br />
<br />
A collection of useful GNU Radio applications designed to aid you is in gr-utils/.<br /><br />
Browse the source code in gnuradio-runtime/python/gnuradio (and other gr-<component>/python directories) to find utilities you can use in your Python code such as filter design code, modulation utilities, and more.<br />
<br />
== Controlling flow graphs ==<br />
<br />
If you have followed the tutorial so far, you will have noticed that a flow graph has always been implemented as a class, derived from <code>gr.top_block</code>. The question remains on how to control one of these classes.<br />
<br />
As mentioned before, deriving the class from gr.top_block brings along all the functionality you might need. To run or stop an existing flow graph, use the following methods:<br />
<br />
{|<br />
|<br />
<br />
| <code>run()</code><br />
|<br />
<br />
| The simplest way to run a flow graph. Calls start(), then wait(). Used to run a flow graph that will stop on its own, or to run a flow graph indefinitely until SIGINT is received.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>start()</code><br />
|<br />
<br />
| Start the contained flow graph. Returns to the caller once the threads are created.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>stop()</code><br />
|<br />
<br />
| Stop the running flow graph. Notifies each thread created by the scheduler to shutdown, then returns to caller.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>wait()</code><br />
|<br />
<br />
| Wait for a flow graph to complete. Flowgraphs complete when either (1) all blocks indicate that they are done, or (2) after stop has been called to request shutdown.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>lock()</code><br />
|<br />
<br />
| Lock a flow graph in preparation for reconfiguration.<br />
|<br />
<br />
|-<br />
|<br />
<br />
| <code>unlock()</code><br />
|<br />
<br />
| Unlock a flow graph in preparation for reconfiguration. When an equal number of calls to lock() and unlock() have occurred, the flow graph will be restarted automatically.<br />
|<br />
<br />
|}<br />
<br />
See the documentation for [http://gnuradio.org/doc/doxygen/classgr_1_1top__block.html <code>gr::top_block</code>] for more details.<br />
<br />
Example:<br />
<br />
<pre><br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
... # Define blocks etc. here<br />
<br />
if __name__ == '__main__':<br />
my_top_block().start()<br />
sleep(5) # Wait 5 secs (assuming sleep was imported!)<br />
my_top_block().stop()<br />
my_top_block().wait() # If the graph is needed to run again, wait() must be called after stop<br />
... # Reconfigure the graph or modify it<br />
my_top_block().start() # start it again<br />
sleep(5) # Wait 5 secs (assuming sleep was imported!)<br />
my_top_block().stop() # since (assuming) the graph will not run again, no need for wait() to be called</pre><br />
These methods help you to control the flow graph from the outside. For many problems this might not be enough: you don't simply want to start or stop a flow graph, you want to reconfigure the way it behaves. For example, imagine your application has a volume control somewhere in your flow graph. This volume control is implemented by inserting a multiplier into the sample stream. This multiplier is of type <code>blocks.multiply_const_ff</code>. If you check the documentation for this kind of of block, you will find a function <code>blocks.multiply_const_ff.set_k()</code> which sets the multiplication factor.<br /><br />
You need to make the settings visible to the outside in order to control it. The simplest way is to make the block an attribute of the flow graph class.<br />
<br />
Example:<br />
<br />
<pre><br />
class my_top_block(gr.top_block):<br />
def __init__(self):<br />
gr.top_block.__init__(self)<br />
... # Define some blocks<br />
self.amp = blocks.multiply_const_ff(1) # Define multiplier block<br />
... # Define more blocks<br />
<br />
self.connect(..., self.amp, ...) # Connect all blocks<br />
<br />
def set_volume(self, volume):<br />
self.amp.set_k(volume)<br />
<br />
if __name__ == '__main__':<br />
my_top_block().start()<br />
sleep(2) # Wait 2 secs (assuming sleep was imported!)<br />
my_top_block.set_volume(2) # Pump up the volume (by factor 2)<br />
sleep(2) # Wait 2 secs (assuming sleep was imported!)<br />
my_top_block().stop()</pre><br />
This example runs the flow graph for 2 seconds and then doubles the volume by accessing the <code>amp</code> block through a member function called <code>set_volume()</code>. Of course, one could have accessed the <code>amp</code> attribute directly, omitting the member function.<br />
<br />
Hint: making blocks attributes of the flow graph is generally a good idea as it makes extending the flow graph with extra member functions easier.<br />
<br />
== Non-flow graph centered applications ==<br />
<br />
Up until now, GNU Radio applications in this tutorial have always been centered around the one class derived from <code>gr.top_block</code>. However, this is not necessarily how GNU Radio needs to be used. GNU Radio was designed to develop DSP applications from Python, so there's no reason to not use the full power of Python when using GNU Radio.<br />
<br />
Python is an extremely powerful language, and new libraries and functionalities are constantly being added. In a way, GNU Radio extends Python with a powerful, real-time-capable DSP library. By combining this with other libraries you have immense functionality right there at your fingertips. For example, by combining GNU Radio with SciPy, a collection of scientific Python libraries, you can record RF signals in real time and do extensive mathematical operations off line, save statistics to a database and so on - all in the same application. Even expensive engineering software such as Matlab might become unnecessary if you combine all these libraries.<br />
<br />
http://www.scipy.org/<br />
<br />
= Advanced Topics =<br />
<br />
If you have really read the previous sections, you already know enough to write your first Python GNU Radio applications. This section will address some slightly more advanced functionalities for Python GNU Radio applications.<br />
<br />
== Dynamic flow graph creation ==<br />
<br />
For most cases, the aforementioned way to define flow graphs is completely adequate. If you need more flexibility in your application, you might want to have even more control over the flow graph from outside the class.<br /><br />
This can be achieved by taking the code out of the <code>+init+()</code> function and simply using <code>gr.top_block</code> as a container. Example:<br />
<br />
<pre><br />
... # We are inside some application<br />
tb = gr.top_block() # Define the container<br />
<br />
block1 = blocks.some_other_block()<br />
block2 = blocks.yet_another_block()<br />
<br />
tb.connect(block1, block2)<br />
<br />
... # The application does some wonderful things here<br />
<br />
tb.start() # Start the flow graph<br />
<br />
... # Do some more incredible and fascinating stuff here</pre><br />
If you are writing some application which needs to dynamically stop a flow graph (reconfigure it, re-start it and so) on this might be a more practical way to do it.<br />
<br />
Examples for this kind of flow graph setup:<br />
<br />
<pre>gr-uhd/apps/hf_explorer/hfx.py</pre><br />
== Command Line Options ==<br />
<br />
Python has its own libraries to parse command line options. See the documentation for the module <code>optparse</code> to find out how to use it.<br />
<br />
GNU Radio extends optparse by new command line option types. Use @from gnuradio.eng_option import eng_option' to import this extension. With eng_option, you have the following types:<br />
<br />
{|<br />
|<br />
<br />
| eng_float<br />
|<br />
<br />
| Like the original float option, but also accepts engineering notation like 101.8M<br />
|<br />
<br />
|-<br />
|<br />
<br />
| subdev<br />
|<br />
<br />
| Only accepts valid subdevice descriptors such as A:0 (To specify a daughterboard on a USRP)<br />
|<br />
<br />
|-<br />
|<br />
<br />
| intx<br />
|<br />
<br />
| Only accepts integers<br />
|<br />
<br />
|}<br />
<br />
If your application supports command line options, it would be ever so nice if you could stick to the GNU Radio conventions for command line options. You can find these (along with more hints for developers) in README.hacking.<br />
<br />
Nearly every GNU Radio example uses this feature. Try dial_tone.py for an easy example.<br />
<br />
== Graphical User Interfaces ==<br />
<br />
If you are a Python expert and also have some experience in writing GUIs for Python (using whatever GUI toolkit you like), you might not even need this section. As mentioned before, GNU Radio merely extends Python with DSP routines - so if you like, just go ahead and write a GUI application, add a GNU Radio flow graph to it and define some interfaces to carry GNU Radio information to your application and vice versa. If you want to plot your data, you could use Matplotlib or Qwt.<br />
<br />
However, sometimes you simply want to write a quick GUI application without bothering with setting up widgets, defining all the menus etc. GNU Radio comes with some predefined classes to help you write graphical GNU Radio applications.<br />
<br />
These modules are based on wxWidgets (or to be precise, wxPython), a platform-independent GUI toolkit. You will need some background in wxPython - but don't worry, it is not that complicated and there are several tutorials available on the net. Check the wxPython website for documentation (http://www.wxpython.org/).<br />
<br />
To use the GNU Radio wxWidgets tools, you need to import some modules:<br />
<br />
<pre><br />
from gnuradio.wxgui import stdgui2, fftsink2, slider, form</pre><br />
Here, 4 components were imported from the gnuradio.wxgui submodule. Here's a quick list of the modules (again, not necessarily complete. You will have to browse the modules or the source code in gr-wxgui/python).<br />
<br />
{|<br />
|<br />
<br />
| stdgui2<br />
|<br />
<br />
| Basic GUI stuff, you always need this<br />
|<br />
<br />
|-<br />
|<br />
<br />
| fftsink2<br />
|<br />
<br />
| Plot FFTs of your data to create spectrum analyzers or whatever<br />
|<br />
<br />
|-<br />
|<br />
<br />
| scopesink2<br />
|<br />
<br />
| Oscilloscope output<br />
|<br />
<br />
|-<br />
|<br />
<br />
| waterfallsink2<br />
|<br />
<br />
| Waterfall output<br />
|<br />
<br />
|-<br />
|<br />
<br />
| numbersink2<br />
|<br />
<br />
| Displays numerical values of incoming data<br />
|<br />
<br />
|-<br />
|<br />
<br />
| form<br />
|<br />
<br />
| Often used input forms (see below)<br />
|<br />
<br />
|}<br />
<br />
Next, we have to define a new flow graph. This time, we don't derive from <code>gr.top_block</code> but from <code>stdgui2.std_top_block</code>:<br />
<br />
<pre><br />
class my_gui_flow_graph(stdgui2.std_top_block):<br />
def __init__(self, frame, panel, vbox, argv):<br />
stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)</pre><br />
As you can see, there's another difference: the constructor gets a couple of new parameters. This is because a <code>stdgui2.std_top_block</code> does not only include flow graph functionality (it is derived from gr.top_block itself), but also directly creates a window with some basic components (like a menu). This is good news for all of those who just want to quickly hack a graphical application: GNU Radio creates the window and everything, you just need to add the widgets. Here's a list of what you can do with these new objects (this probably won't mean much to you if you have no idea about GUI programming):<br />
<br />
{|<br />
|<br />
<br />
| frame<br />
|<br />
<br />
| The wx.Frame of your window. You can get at the predefined menu by using frame.GetMenuBar()<br />
|<br />
<br />
|-<br />
|<br />
<br />
| panel<br />
|<br />
<br />
| A panel, placed in @frame', to hold all your wxControl widgets<br />
|<br />
<br />
|-<br />
|<br />
<br />
| vbox<br />
|<br />
<br />
| A vertical box sizer (wx.BoxSizer(wx.VERTICAL) is how it is defined), used to align your widgets in the panel<br />
|<br />
<br />
|-<br />
|<br />
<br />
| argv<br />
|<br />
<br />
| The command line arguments<br />
|<br />
<br />
|}<br />
<br />
Now you have all you need to create your GUI. You can simply add new box sizers and widgets to vbox, change the menu or whatever. Some typical functions have been simplified further in the GNU Radio GUI library <code>form</code>.<br />
<br />
form has a great number of input widgets: <code>form.static_text_field()</code> for static text field (display only), <code>form.float_field()</code>, to input float values, <code>form.text_field()</code> to input text, <code>form.checkbox_field()</code> for checkboxes, <code>form.radiobox_field()</code> for radioboxes etc. Check the source code of gr-wxgui/python/form.py for the complete list. Most of these calls pass most of their arguments to the appropriate wxPython objects, so the function arguments are quite self-explanatory.<br /><br />
See one of the examples mentioned below on how to add widgets using form.<br />
<br />
Probably the most useful part of <code>gnuradio.wxgui</code> is the possibility to directly plot incoming data. To do this, you need one of the sinks that come with <code>gnuradio.wxgui</code>, such as <code>fftsink2</code>. These sinks work just as any other GNU Radio sink, but also have properties needed for use with wxPython. Example:<br />
<br />
<pre><br />
from gnuradio.wxgui import stdgui2, fftsink2<br />
<br />
<br />
# App gets defined here ...<br />
<br />
# FFT display (pseudo-spectrum analyzer)<br />
my_fft = fftsink2.fft_sink_f(panel, title=&quot;FFT of some Signal&quot;, fft_size=512,<br />
sample_rate=sample_rate, ref_level=0, y_per_div=20)<br />
self.connect(source_block, my_fft)<br />
vbox.Add(my_fft.win, 1, wx.EXPAND)</pre><br />
First, the block is defined (<code>fftsink2.fft_sink_f</code>). Apart from typical DSP parameters such as the sampling rate, it also needs the <code>panel</code> object which is passed to the constructor. Next, the block is connected to a source. Finally, the FFT window (<code>my_fft.win</code>) is placed inside the <code>vbox</code> BoxSizer to actually display it. Remember that a signal block output can be connected to any amount of inputs.<br />
<br />
Finally, the whole thing needs to be started. Because we need an <code>wx.App()</code> to run the GUI, the start-up code is a bit different from a regular flow graph:<br />
<br />
<pre><br />
if __name__ == '__main__':<br />
app = stdgui2.stdapp(my_gui_flow_graph, &quot;GUI GNU Radio Application&quot;)<br />
app.MainLoop()</pre><br />
<code>stdgui2.stdapp()</code> creates the <code>wx.App</code> with <code>my_gui_flow_graph</code> (the first argument). The window title is set to &quot;GUI GNU Radio Application&quot;.<br />
<br />
Examples for simple GNU Radio GUIs:<br />
<br />
<pre>gr-uhd/apps/uhd_fft<br />
gr-audio/examples/python/audio_fft.py<br />
./gr-uhd/examples/python/usrp_am_mw_rcv.py</pre><br />
And many more.<br />
<br />
= What next? =<br />
<br />
Young Padawan, no more there is I can teach you. If you have any more questions on how to write GNU Radio applications in Python, there are still a number of resources you can use:<br />
<br />
* Use the source. Especially the examples in gr-<component>/examples/ and gr-utils/ can be very helpful.<br />
* Check the [http://gnuradio.org/redmine/projects/gnuradio/wiki/MailingLists mailing list] archives. Chances are very high your problem has been asked before.<br />
<br />
[[Category:Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=TutorialsCoreConcepts&diff=6368TutorialsCoreConcepts2019-11-17T22:49:32Z<p>Cmrincon: /* Sampling rates */</p>
<hr />
<div>= Core concepts of GNU Radio =<br />
<br />
This is a very basic introductory tutorial to GNU Radio. You should definitely read this before reading anything else. Even if you've heard of all of this, at least skim over to see if there's something you've missed.<br />
<br />
== Flow graphs -- and what they're made of ==<br />
<br />
Before we're going anywhere, first we need to understand the most basic concepts about GNU Radio: ''flow graphs'' and ''blocks''.<br />
<br />
Flow graphs are graphs (as in [http://en.wikipedia.org/wiki/Graph_Theory graph theory]) through which data flows. Many GNU Radio applications contain nothing other than a flow graph. The nodes of such a graph are called ''blocks'', and the data flows along the edges.<br />
<br />
Any actual signal processing is done in the blocks. Ideally, every block does exactly one job - this way GNU Radio stays modular and flexible. Blocks are usually written in C++ (might also be Python); [[OutOfTreeModules|writing new blocks]] is not very difficult.<br />
<br />
In order to illuminate this diffuse topic a little, let's start with an example (all these examples were created with the [[GNURadioCompanion|GNU Radio Companion]] (GRC), a graphical user interface to GNU Radio).<br />
<br />
Here, we have three blocks (the rectangles). Data flows from left to right in this example, meaning that it originates in the audio source, passes through the low pass filter and ends in a file that gets written to hard disk.<br />
<br />
[[File:Fg1-audiolpf.png|500px|]]<br />
<br />
The blocks are connected at ''ports''. The first block has no input port, it produces samples. Such a block, with only output ports, is called a ''source''. In an analog fashion, the final block, with no outputs, is called a ''sink''.<br />
<br />
Sometimes, this is confusing: from the user's perspective, the audio block (which takes samples from the sound card) is just a part of the processing. When we're talking about sinks and sources, we always mean from the flow graph's perspective.<br />
<br />
So what's happening here. The audio source block is connected to the sound card driver and outputs audio samples. These samples are connected to the low pass filter, which processes them further. Finally, the samples are passed to a block which writes them to a WAV file.<br />
<br />
== Items ==<br />
<br />
In general, we call whatever a block outputs an ''item''. In the previous example, one item was a float value representing one sample produced by the audio driver. However, an item can be ''anything'' that can be represented digitally. The most common types of samples are real samples (as before), complex samples (the most common type in software defined radio), integer types, and vectors of these scalar types.<br />
<br />
To understand this latter concept, consider an FFT analysis. Say we want to perform an FFT to a signal before we save it a file. Of course, we need a certain number of samples at a time to calculate an FFT; unlike the filters, it does not work sample-wise.<br />
<br />
Here's how it works:<br />
<br />
[[File:Fg2-fftvec.png|700px|]]<br />
<br />
There's a new block in here called 'Stream to vector'. What's special about this is that its input type is different to its output type. This block takes 1024 samples (i.e. 1024 items) and outputs them as one vector of 1024 samples (which is one item). The complex FFT outputs are then converted to their magnitude squared; which is a real data type (note how we use different colors at the ports to indicate different data types).<br />
<br />
So remember: an ''item'' can be anything, a sample, a bunch of bits, a set of filter coefficients or whatever.<br />
<br />
'''Summary:'''<br />
<br />
Here's what you should know by now:<br />
<br />
* All signal processing in GNU Radio is done through flow graphs.<br />
* A flow graph consists of blocks. A block does one signal processing operation, such as filtering, adding signals, transforming, decoding, hardware access or many others.<br />
* Data passes between blocks in various formats, complex or real integers, floats or basically any kind of data type you can define.<br />
* Every flow graph needs at least one sink and source.<br />
<br />
== So what does GNU Radio do? ==<br />
<br />
First, this is what you do: you design the flow graph, choose the blocks, define the connections and tell GNU Radio about all of this. GNU Radio comes in twice, here: first, it supplies you with a multitude of blocks. Once the flow graph is defined, it executes the flow graph by calling the blocks one after another and makes sure the items are passed from one block to another.<br />
<br />
== Sampling rates ==<br />
<br />
Of course, if you're reading this, you know what sampling rates are (if you don't, go figure that out). Let's see how these relate to flow graphs. In the first example, the audio source has a fixed sampling rate of 32ksps. Because the filter doesn't change the sampling rate, the same sampling rate is used through the entire flow graph.<br />
<br />
In the second example, the second block (stream to vector) produces one item for every 1024 input items. So, the rate at which it produces items is 1024 times smaller then the rate at which it consumes items (the fact that it actually produces ''bytes'' at the same rate it consumes them is irrelevant here). Such a block is called a ''decimator'', well, because it decimates the item rate. A block which outputs more items than it receives is called an ''interpolator''. If it produces and consumes at the same rate, it's a ''sync'' block.<br />
<br />
Now, let's return to the second example. As mentioned, it has different sampling rates throughout the flow graph. But what's the base sampling rate?<br />
<br />
OK, get ready: there's no such thing. As long as there is no hardware clock present which fixes the rate, sampling rate is meaningless--only relative rates (i.e. input to output rates are important. Your computer may handle the samples as fast as it wants (note that this can cause your computer to lock up by allocating 100% of CPU cycles to your signal processing).<br />
<br />
Here's another example:<br />
<br />
[[File:Fg3-dialtone.png|300px|]]<br />
<br />
First of all, what's new here is that the sink has two inputs. Each port goes to one channel (left, right) of the sound card, which runs at a fixed sampling rate.<br />
<br />
== More on blocks (and atomicity) ==<br />
<br />
Let's go back to the blocks. The biggest part of GNU Radio is the large number of blocks that GNU Radio provides. When you start using GNU Radio, you'll be connecting block after block. Sooner or later, you'll need a block that's ''not'' available in GNU Radio, which you can write yourself. [[OutOfTreeModules | It's not hard]].<br />
<br />
The question is, how much do you put into one block? Ideally, blocks are as atomic as possible; every block does exactly one job - this way GNU Radio stays modular and flexible. However, sometimes this just doesn't work. Some blocks do a lot of work at once. You'll probably find there's a performance vs. modularity tradeoff.<br />
<br />
== A note for Simulink users ==<br />
<br />
Simulink-users: Ignore all you know about frame- vs. sample-based processing.<br />
<br />
This is a special section only for Simulink-users. In Simulink, flow graphs can be configured to run either frame-based, or sample-based. The difference is that in the sample-based model, samples are passed from block to block individually, and therefore the control over the signal processing stream is maximal. However, this comes at a performance loss, and therefore Simulink introduces the frame-based processing.<br />
<br />
Now, when you're coming from Simulink: '''forget all of this'''. It just doesn't make sense in GNU Radio. In GNU Radio, there is only ''item-based'', and, quite often, an item is a sample (but it could also be a vector). The item sizes are a logical description of what you get at the input ports.<br />
<br />
The reason GNU Radio does not have this performance loss (although it deals with individual items) is because it tries to process as many items as possible at once. In a sense, it is both sample-based and frame-based.<br />
<br />
The downside of the GNU Radio approach is that it is not trivial to introduce recursive flow graphs. The upside is a massive performance improvement.<br />
<br />
== Metadata ==<br />
<br />
A stream of samples is much more interesting when there is parsable metadata connected to that stream, such as the time of reception, centre frequency, sampling rate or even protocol-specific information such as node identification.<br />
<br />
In GNU Radio, adding metadata to sample streams is done via a mechanism called '''stream tags'''. A stream tag is an object that is connected to a specific item (e.g. a sample). This can be a scalar value of any type, a vector, a list, a dictionary or whatever the user specifies.<br />
<br />
When saving streams to disk, the metadata can be saved as well (see also the [http://gnuradio.org/doc/doxygen/page_metadata.html Metadata Documentation Page]).<br />
<br />
== Streams vs. Messages: Passing PDUs ==<br />
<br />
All the blocks presented so far operate as &quot;infinite stream&quot; blocks, i.e., they simply continue working as long as items are fed into their inputs. The low pass filter is a good example: Every new item is interpreted as a new sample, and the output is always the low-pass filtered version of the input. It does not care about the content of the signal, be it noise, data or whatever.<br />
<br />
When handling packets (or PDUs, protocol data units), such a behaviour is not enough. There must be a way to identify PDU boundaries, i.e. tell which byte is the first of this packet and how long it is.<br />
<br />
GNU Radio supports two ways to do this: Message passing and tagged stream blocks.<br />
<br />
The first is an asynchronous method to directly pass PDUs from one block to another. On a MAC layer, this is probably the preferred behaviour. A block could receive a PDU, add a packet header, and pass the entire packet (including the new header) as a new PDU to another block (see also the [[http://gnuradio.org/doc/doxygen/page_msg_passing.html message passing manual page]]).<br />
<br />
Tagged stream blocks are regular streaming blocks which use stream tags to identify PDU boundaries (see the [http://gnuradio.org/doc/doxygen/page_tagged_stream_blocks.html tagged stream blocks manual page]]). This allows mixing blocks which know about PDUs and blocks that don't care about them. There are also blocks to switch between message passing and tagged stream blocks.<br />
<br />
[[Category:Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=TutorialsCoreConcepts&diff=6367TutorialsCoreConcepts2019-11-17T22:39:08Z<p>Cmrincon: /* Items */</p>
<hr />
<div>= Core concepts of GNU Radio =<br />
<br />
This is a very basic introductory tutorial to GNU Radio. You should definitely read this before reading anything else. Even if you've heard of all of this, at least skim over to see if there's something you've missed.<br />
<br />
== Flow graphs -- and what they're made of ==<br />
<br />
Before we're going anywhere, first we need to understand the most basic concepts about GNU Radio: ''flow graphs'' and ''blocks''.<br />
<br />
Flow graphs are graphs (as in [http://en.wikipedia.org/wiki/Graph_Theory graph theory]) through which data flows. Many GNU Radio applications contain nothing other than a flow graph. The nodes of such a graph are called ''blocks'', and the data flows along the edges.<br />
<br />
Any actual signal processing is done in the blocks. Ideally, every block does exactly one job - this way GNU Radio stays modular and flexible. Blocks are usually written in C++ (might also be Python); [[OutOfTreeModules|writing new blocks]] is not very difficult.<br />
<br />
In order to illuminate this diffuse topic a little, let's start with an example (all these examples were created with the [[GNURadioCompanion|GNU Radio Companion]] (GRC), a graphical user interface to GNU Radio).<br />
<br />
Here, we have three blocks (the rectangles). Data flows from left to right in this example, meaning that it originates in the audio source, passes through the low pass filter and ends in a file that gets written to hard disk.<br />
<br />
[[File:Fg1-audiolpf.png|500px|]]<br />
<br />
The blocks are connected at ''ports''. The first block has no input port, it produces samples. Such a block, with only output ports, is called a ''source''. In an analog fashion, the final block, with no outputs, is called a ''sink''.<br />
<br />
Sometimes, this is confusing: from the user's perspective, the audio block (which takes samples from the sound card) is just a part of the processing. When we're talking about sinks and sources, we always mean from the flow graph's perspective.<br />
<br />
So what's happening here. The audio source block is connected to the sound card driver and outputs audio samples. These samples are connected to the low pass filter, which processes them further. Finally, the samples are passed to a block which writes them to a WAV file.<br />
<br />
== Items ==<br />
<br />
In general, we call whatever a block outputs an ''item''. In the previous example, one item was a float value representing one sample produced by the audio driver. However, an item can be ''anything'' that can be represented digitally. The most common types of samples are real samples (as before), complex samples (the most common type in software defined radio), integer types, and vectors of these scalar types.<br />
<br />
To understand this latter concept, consider an FFT analysis. Say we want to perform an FFT to a signal before we save it a file. Of course, we need a certain number of samples at a time to calculate an FFT; unlike the filters, it does not work sample-wise.<br />
<br />
Here's how it works:<br />
<br />
[[File:Fg2-fftvec.png|700px|]]<br />
<br />
There's a new block in here called 'Stream to vector'. What's special about this is that its input type is different to its output type. This block takes 1024 samples (i.e. 1024 items) and outputs them as one vector of 1024 samples (which is one item). The complex FFT outputs are then converted to their magnitude squared; which is a real data type (note how we use different colors at the ports to indicate different data types).<br />
<br />
So remember: an ''item'' can be anything, a sample, a bunch of bits, a set of filter coefficients or whatever.<br />
<br />
'''Summary:'''<br />
<br />
Here's what you should know by now:<br />
<br />
* All signal processing in GNU Radio is done through flow graphs.<br />
* A flow graph consists of blocks. A block does one signal processing operation, such as filtering, adding signals, transforming, decoding, hardware access or many others.<br />
* Data passes between blocks in various formats, complex or real integers, floats or basically any kind of data type you can define.<br />
* Every flow graph needs at least one sink and source.<br />
<br />
== So what does GNU Radio do? ==<br />
<br />
First, this is what you do: you design the flow graph, choose the blocks, define the connections and tell GNU Radio about all of this. GNU Radio comes in twice, here: first, it supplies you with a multitude of blocks. Once the flow graph is defined, it executes the flow graph by calling the blocks one after another and makes sure the items are passed from one block to another.<br />
<br />
== Sampling rates ==<br />
<br />
Of course, if you're reading this, you know what sampling rates are (if you don't, go figure that out). Let's see how these relate to flow graphs. In the first example, the audio source has a fixed sampling rate of 32ksps. Because the filter doesn't change the sampling rate, the same sampling rate is used through the entire flow graph.<br />
<br />
In the second example, the second block (stream to vector) produces one item for every 1024 input items. So, the rate at which it produces items is 1024 times smaller then the rate at which it consumes items (the fact that it actually produces ''bytes'' at the same rate it consumes them is irrelevant here). Such a block is called a ''decimator'', well, because it decimates the item rate. A block which outputs more items than it receives is called an ''interpolator''. If it produces and consumes at the same rate, it's a ''sync'' block.<br />
<br />
Now, let's return to the second example. As mentioned, it has different sampling rates throughout the flow graph. But what's the base sampling rate?<br />
<br />
OK, get ready: there's no such thing. As long as there is no hardware clock present which fixes the rate, sampling rate is meaningless--only relative rates (i.e. input to output rates are important. Your computer may handle the samples as fast as it wants (note that this can cause your computer to lock up by allocating 100% of CPU cycles to your signal processing).<br />
<br />
Here's another example:<br />
<br />
p=. [[File:Fg3-dialtone.png|300px|]]<br />
<br />
First of all, what's new here is that the sink has two inputs. Each port goes to one channel (left, right) of the sound card, which runs at a fixed sampling rate.<br />
<br />
== More on blocks (and atomicity) ==<br />
<br />
Let's go back to the blocks. The biggest part of GNU Radio is the large number of blocks that GNU Radio provides. When you start using GNU Radio, you'll be connecting block after block. Sooner or later, you'll need a block that's ''not'' available in GNU Radio, which you can write yourself. [[OutOfTreeModules | It's not hard]].<br />
<br />
The question is, how much do you put into one block? Ideally, blocks are as atomic as possible; every block does exactly one job - this way GNU Radio stays modular and flexible. However, sometimes this just doesn't work. Some blocks do a lot of work at once. You'll probably find there's a performance vs. modularity tradeoff.<br />
<br />
== A note for Simulink users ==<br />
<br />
Simulink-users: Ignore all you know about frame- vs. sample-based processing.<br />
<br />
This is a special section only for Simulink-users. In Simulink, flow graphs can be configured to run either frame-based, or sample-based. The difference is that in the sample-based model, samples are passed from block to block individually, and therefore the control over the signal processing stream is maximal. However, this comes at a performance loss, and therefore Simulink introduces the frame-based processing.<br />
<br />
Now, when you're coming from Simulink: '''forget all of this'''. It just doesn't make sense in GNU Radio. In GNU Radio, there is only ''item-based'', and, quite often, an item is a sample (but it could also be a vector). The item sizes are a logical description of what you get at the input ports.<br />
<br />
The reason GNU Radio does not have this performance loss (although it deals with individual items) is because it tries to process as many items as possible at once. In a sense, it is both sample-based and frame-based.<br />
<br />
The downside of the GNU Radio approach is that it is not trivial to introduce recursive flow graphs. The upside is a massive performance improvement.<br />
<br />
== Metadata ==<br />
<br />
A stream of samples is much more interesting when there is parsable metadata connected to that stream, such as the time of reception, centre frequency, sampling rate or even protocol-specific information such as node identification.<br />
<br />
In GNU Radio, adding metadata to sample streams is done via a mechanism called '''stream tags'''. A stream tag is an object that is connected to a specific item (e.g. a sample). This can be a scalar value of any type, a vector, a list, a dictionary or whatever the user specifies.<br />
<br />
When saving streams to disk, the metadata can be saved as well (see also the [http://gnuradio.org/doc/doxygen/page_metadata.html Metadata Documentation Page]).<br />
<br />
== Streams vs. Messages: Passing PDUs ==<br />
<br />
All the blocks presented so far operate as &quot;infinite stream&quot; blocks, i.e., they simply continue working as long as items are fed into their inputs. The low pass filter is a good example: Every new item is interpreted as a new sample, and the output is always the low-pass filtered version of the input. It does not care about the content of the signal, be it noise, data or whatever.<br />
<br />
When handling packets (or PDUs, protocol data units), such a behaviour is not enough. There must be a way to identify PDU boundaries, i.e. tell which byte is the first of this packet and how long it is.<br />
<br />
GNU Radio supports two ways to do this: Message passing and tagged stream blocks.<br />
<br />
The first is an asynchronous method to directly pass PDUs from one block to another. On a MAC layer, this is probably the preferred behaviour. A block could receive a PDU, add a packet header, and pass the entire packet (including the new header) as a new PDU to another block (see also the [[http://gnuradio.org/doc/doxygen/page_msg_passing.html message passing manual page]]).<br />
<br />
Tagged stream blocks are regular streaming blocks which use stream tags to identify PDU boundaries (see the [http://gnuradio.org/doc/doxygen/page_tagged_stream_blocks.html tagged stream blocks manual page]]). This allows mixing blocks which know about PDUs and blocks that don't care about them. There are also blocks to switch between message passing and tagged stream blocks.<br />
<br />
[[Category:Tutorials]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=InstallingGR&diff=6366InstallingGR2019-11-17T21:05:20Z<p>Cmrincon: /* Windows */</p>
<hr />
<div>= From Binaries =<br />
<br />
The recommended way to install GNU Radio on most platforms is using already available binary packages. For some platforms there are no binaries provided by available package managers or the GNU Radio project. In these cases please contact maintainer of the package manager or the GNU Radio project to find a sensible way to provide binaries for your platform.<br />
<br />
In addition to using binaries, GNU Radio can be installed:<br />
<br />
# '''[[#From_Source|From source]]''' (for those who want full control)<br />
# '''[[#Using_PyBOMBS|Using PyBOMBS]]''' (for those who want it built from source and/or installed to a specific directory using a script)<br />
# '''[[#Bootable_DVD_with_GNU_Radio_Pre-installed|Premade Linux Live DVD/USB]]''' (great for someone without a Linux machine who just wants to try it out)<br />
<br />
== Linux ==<br />
<br />
Most distributions contain a package named <code>gnuradio</code> or similar in their standard repositories. For most use cases it is enough to install this package and start developing.<br />
<br />
The development of GNU Radio is can be fast-paced, and binaries provided by your distribution may be outdated. '''Do check if the version you're installing is up to date! Sometimes old versions are not updated in the packaging systems.''' If you find a bug in a older GNU Radio version, please check if the bug still exists in the newer version of GNU Radio before filing it.<br />
<br />
If the version shipped in your distribution is outdated please contact the corresponding maintainer to update it in the packaging system.<br />
<br />
Examples of how to install GNU Radio in various Linux distributions (click each one for more installation details):<br />
{|class="wikitable" style="margin: auto; width: 90%;"<br />
!scope="col"|Distribution<br />
!scope="col"|Command<br />
|-<br />
| [[UbuntuInstall|Debian/Ubuntu and derivates]]<br />
| <pre>$ apt install gnuradio</pre><br />
|-<br />
| [[FedoraInstall|Fedora]]<br />
| <pre>$ dnf install gnuradio</pre><br />
|-<br />
| RHEL/CentOS<br />
| <pre>$ yum install gnuradio</pre><br />
|-<br />
| [[ArchInstall|Archlinux]]<br />
| <pre>$ pacman -S gnuradio</pre><br />
|-<br />
| [[GentooInstall|Gentoo Linux]]<br />
| <pre>$ emerge net-wireless/gnuradio</pre><br />
|-<br />
| [[SuseInstall|Suse Linux]]<br />
|<br />
|-<br />
|}<br />
<br />
On other distributions, simply use the appropriate package management command to install the <code>gnuradio</code> package and add it to this list. If you need newer versions or have a different platform please contact the package maintainer of your distribution or raise your issue on the mailing list.<br />
<br />
=== Ubuntu PPA Installation ===<br />
For Ubuntu, the latest builds (both released and pulled from master branch) are maintained as PPAs on launchpad. Be sure to uninstall gnuradio first.<br />
<br />
To access the latest released version, add the gnuradio/gnuradio-releases ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases</pre><br />
<br />
To access the latest from the master branch, add the gnuradio/gnuradio-master ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-master</pre><br />
<br />
To access the 3.7 released version (legacy), add the gnuradio/gnuradio-releases-3.7 ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases-3.7</pre><br />
<br />
Then, update the apt sources, and install gnuradio<br />
<pre>$ sudo apt-get update</pre><br />
<pre>$ sudo apt install gnuradio</pre><br />
<br />
== Windows ==<br />
<br />
Binary installers are now available for 3.8.0.0, download them [http://www.gcndevelopment.com/gnuradio/downloads.htm here].<br /><br />
If you need to install GNU Radio from source refer to the [[WindowsInstall|Windows install guide]].<br />
<br />
Note: We do not officially support Windows. We do our best to provide installation instructions and work out build bugs on Windows when they are reported and patches received. As new versions of GNU Radio, its dependencies, and Windows itself come out, however, keeping all of them working together is beyond the scope of what the project currently supports. User updates to the above wiki installation instructions are very welcome.<br />
<br />
== Mac OS X ==<br />
<br />
Refer to the [[MacInstall|Mac OS X install guide page]].<br />
<br />
= From Source =<br />
<br />
Binary installation should be sufficient for most users, and certainly for anyone who is new to GNU Radio. However, if you have special requirements, want the latest version, or the binary packages are not working for you, you may want to install GNU Radio from source.<br />
<br />
== Notes ==<br />
<br />
* By default GNU Radio will be installed in the /usr/local directory.<br />
* Running and developing out-of-tree modules does not require GNU Radio to be installed from source. <br />
* If you are using Ubuntu, see [[UbuntuInstall#Install_the_Pre-Requisites]] for installing dependencies / pre-requisites.<br />
* If you want to use GNU Radio with a USRP, install the UHD package or follow the [https://files.ettus.com/manual/page_build_guide.html UHD Manual Build Guide].<br />
* To install on a Raspberry Pi, see [[InstallingGRFromSource on Raspberry Pi]].<br />
<br />
== To install system wide ==<br />
<br />
<p>It doesn't matter which directory you start in; it can be home, ~/Downloads, etc.; but <b>gnuradio will become a sub-directory of that starting point.</b></p><br />
* <code>git clone --recursive https://github.com/gnuradio/gnuradio.git</code><br />
* <code>cd gnuradio</code><br />
* <code>git checkout maint-3.8</code> # change `maint-3.8` if you want to use a different version; see [https://github.com/gnuradio/gnuradio/releases releases]<br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
* <code>cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ../</code> # see other options below<br />
* Note: if your computer has multiple cores, you can use the -j option for 'make' to speed compilation, e.g. <code>make -j3</code> but specify one less than the total so the system does not appear to 'freeze'.<br />
* <code>make</code><br />
* <code>make test</code><br />
* <code>sudo make install</code><br />
* <code>sudo ldconfig</code><br />
<br />
== Common cmake flags ==<br />
<br />
* <code>-DENABLE_GR_XXX=ON</code> This enables (or disables for =OFF) the GNU Radio component named XXX. You might not need all of them, and this way, you can compile quicker.<br />
* <code>-DCMAKE_INSTALL_PREFIX=XXX</code> Install your stuff to XXX.<br />
* <code>-DCMAKE_BUILD_TYPE=Debug</code> This causes gcc to add debug symbols to all binaries. Useful for debugging (otherwise, it decreases efficiency!)<br />
* <code>-DPYTHON_EXECUTABLE=/usr/bin/python{2,3}</code> This selects the Python version and executable to be used during build time and will determine which Python libraries will be used for building the Python bindings.<br />
<p>For a list of additional cmake flags, as well as minimum versions of dependencies, see [https://www.gnuradio.org/doc/doxygen/build_guide.html]</p><br />
<br />
= Using PyBOMBS =<br />
<br />
PyBOMBS is good at building GNU Radio, UHD, and various Out of Tree (OOT) modules from source and then installing into a specified user directory rather than in the system files. PyBOMBS detects the user's Operating System and loads all of the prerequisites in the first stage of the build.<br />
<br />
The PyBOMBS documentation is in the PyBOMBS [https://github.com/duggabe/pybombs#pybombs README].<br />
<br />
= Bootable DVD with GNU Radio Pre-installed =<br />
<br />
One can download a fully pre-configured [[GNURadioLiveDVD]] that allows trying out GNU Radio without installing GNU Radio onto a PC. To make a permanent installation of GNU Radio, one will still needs to choose from the options described above.<br />
<br />
= OK, it's installed, what now? =<br />
<br />
If the installation worked without any trouble, you're ready to use GNU Radio! If you have no idea how to do that, read the [[HowToUse|page on how to use GNU Radio]]. You probably want to connect some [[Hardware]] to your computer to try and receive or transmit stuff. If you or your group would like to get a professional jump start on using GNU Radio and the USRP, [http://corganlabs.com Corgan Labs] offers a 3-day, hands-on training class that can be held at your own location.<br />
<br />
[[Category:Installation]]<br />
[[Category:Guide]]</div>Cmrinconhttps://wiki.gnuradio.org/index.php?title=InstallingGR&diff=6365InstallingGR2019-11-17T20:55:02Z<p>Cmrincon: /* Ubuntu PPA Installation */</p>
<hr />
<div>= From Binaries =<br />
<br />
The recommended way to install GNU Radio on most platforms is using already available binary packages. For some platforms there are no binaries provided by available package managers or the GNU Radio project. In these cases please contact maintainer of the package manager or the GNU Radio project to find a sensible way to provide binaries for your platform.<br />
<br />
In addition to using binaries, GNU Radio can be installed:<br />
<br />
# '''[[#From_Source|From source]]''' (for those who want full control)<br />
# '''[[#Using_PyBOMBS|Using PyBOMBS]]''' (for those who want it built from source and/or installed to a specific directory using a script)<br />
# '''[[#Bootable_DVD_with_GNU_Radio_Pre-installed|Premade Linux Live DVD/USB]]''' (great for someone without a Linux machine who just wants to try it out)<br />
<br />
== Linux ==<br />
<br />
Most distributions contain a package named <code>gnuradio</code> or similar in their standard repositories. For most use cases it is enough to install this package and start developing.<br />
<br />
The development of GNU Radio is can be fast-paced, and binaries provided by your distribution may be outdated. '''Do check if the version you're installing is up to date! Sometimes old versions are not updated in the packaging systems.''' If you find a bug in a older GNU Radio version, please check if the bug still exists in the newer version of GNU Radio before filing it.<br />
<br />
If the version shipped in your distribution is outdated please contact the corresponding maintainer to update it in the packaging system.<br />
<br />
Examples of how to install GNU Radio in various Linux distributions (click each one for more installation details):<br />
{|class="wikitable" style="margin: auto; width: 90%;"<br />
!scope="col"|Distribution<br />
!scope="col"|Command<br />
|-<br />
| [[UbuntuInstall|Debian/Ubuntu and derivates]]<br />
| <pre>$ apt install gnuradio</pre><br />
|-<br />
| [[FedoraInstall|Fedora]]<br />
| <pre>$ dnf install gnuradio</pre><br />
|-<br />
| RHEL/CentOS<br />
| <pre>$ yum install gnuradio</pre><br />
|-<br />
| [[ArchInstall|Archlinux]]<br />
| <pre>$ pacman -S gnuradio</pre><br />
|-<br />
| [[GentooInstall|Gentoo Linux]]<br />
| <pre>$ emerge net-wireless/gnuradio</pre><br />
|-<br />
| [[SuseInstall|Suse Linux]]<br />
|<br />
|-<br />
|}<br />
<br />
On other distributions, simply use the appropriate package management command to install the <code>gnuradio</code> package and add it to this list. If you need newer versions or have a different platform please contact the package maintainer of your distribution or raise your issue on the mailing list.<br />
<br />
=== Ubuntu PPA Installation ===<br />
For Ubuntu, the latest builds (both released and pulled from master branch) are maintained as PPAs on launchpad. Be sure to uninstall gnuradio first.<br />
<br />
To access the latest released version, add the gnuradio/gnuradio-releases ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases</pre><br />
<br />
To access the latest from the master branch, add the gnuradio/gnuradio-master ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-master</pre><br />
<br />
To access the 3.7 released version (legacy), add the gnuradio/gnuradio-releases-3.7 ppa (removing other gnuradio ppas if already configured)<br />
<br />
<pre>$ sudo add-apt-repository ppa:gnuradio/gnuradio-releases-3.7</pre><br />
<br />
Then, update the apt sources, and install gnuradio<br />
<pre>$ sudo apt-get update</pre><br />
<pre>$ sudo apt install gnuradio</pre><br />
<br />
== Windows ==<br />
<br />
Binary installers are now available for 3.7.13.5, download them [http://www.gcndevelopment.com/gnuradio/downloads.htm here].<br /><br />
If you need to install GNU Radio from source refer to the [[WindowsInstall|Windows install guide]].<br />
<br />
Note: We do not officially support Windows. We do our best to provide installation instructions and work out build bugs on Windows when they are reported and patches received. As new versions of GNU Radio, its dependencies, and Windows itself come out, however, keeping all of them working together is beyond the scope of what the project currently supports. User updates to the above wiki installation instructions are very welcome.<br />
<br />
== Mac OS X ==<br />
<br />
Refer to the [[MacInstall|Mac OS X install guide page]].<br />
<br />
= From Source =<br />
<br />
Binary installation should be sufficient for most users, and certainly for anyone who is new to GNU Radio. However, if you have special requirements, want the latest version, or the binary packages are not working for you, you may want to install GNU Radio from source.<br />
<br />
== Notes ==<br />
<br />
* By default GNU Radio will be installed in the /usr/local directory.<br />
* Running and developing out-of-tree modules does not require GNU Radio to be installed from source. <br />
* If you are using Ubuntu, see [[UbuntuInstall#Install_the_Pre-Requisites]] for installing dependencies / pre-requisites.<br />
* If you want to use GNU Radio with a USRP, install the UHD package or follow the [https://files.ettus.com/manual/page_build_guide.html UHD Manual Build Guide].<br />
* To install on a Raspberry Pi, see [[InstallingGRFromSource on Raspberry Pi]].<br />
<br />
== To install system wide ==<br />
<br />
<p>It doesn't matter which directory you start in; it can be home, ~/Downloads, etc.; but <b>gnuradio will become a sub-directory of that starting point.</b></p><br />
* <code>git clone --recursive https://github.com/gnuradio/gnuradio.git</code><br />
* <code>cd gnuradio</code><br />
* <code>git checkout maint-3.8</code> # change `maint-3.8` if you want to use a different version; see [https://github.com/gnuradio/gnuradio/releases releases]<br />
* <code>mkdir build</code><br />
* <code>cd build</code><br />
* <code>cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=/usr/bin/python3 ../</code> # see other options below<br />
* Note: if your computer has multiple cores, you can use the -j option for 'make' to speed compilation, e.g. <code>make -j3</code> but specify one less than the total so the system does not appear to 'freeze'.<br />
* <code>make</code><br />
* <code>make test</code><br />
* <code>sudo make install</code><br />
* <code>sudo ldconfig</code><br />
<br />
== Common cmake flags ==<br />
<br />
* <code>-DENABLE_GR_XXX=ON</code> This enables (or disables for =OFF) the GNU Radio component named XXX. You might not need all of them, and this way, you can compile quicker.<br />
* <code>-DCMAKE_INSTALL_PREFIX=XXX</code> Install your stuff to XXX.<br />
* <code>-DCMAKE_BUILD_TYPE=Debug</code> This causes gcc to add debug symbols to all binaries. Useful for debugging (otherwise, it decreases efficiency!)<br />
* <code>-DPYTHON_EXECUTABLE=/usr/bin/python{2,3}</code> This selects the Python version and executable to be used during build time and will determine which Python libraries will be used for building the Python bindings.<br />
<p>For a list of additional cmake flags, as well as minimum versions of dependencies, see [https://www.gnuradio.org/doc/doxygen/build_guide.html]</p><br />
<br />
= Using PyBOMBS =<br />
<br />
PyBOMBS is good at building GNU Radio, UHD, and various Out of Tree (OOT) modules from source and then installing into a specified user directory rather than in the system files. PyBOMBS detects the user's Operating System and loads all of the prerequisites in the first stage of the build.<br />
<br />
The PyBOMBS documentation is in the PyBOMBS [https://github.com/duggabe/pybombs#pybombs README].<br />
<br />
= Bootable DVD with GNU Radio Pre-installed =<br />
<br />
One can download a fully pre-configured [[GNURadioLiveDVD]] that allows trying out GNU Radio without installing GNU Radio onto a PC. To make a permanent installation of GNU Radio, one will still needs to choose from the options described above.<br />
<br />
= OK, it's installed, what now? =<br />
<br />
If the installation worked without any trouble, you're ready to use GNU Radio! If you have no idea how to do that, read the [[HowToUse|page on how to use GNU Radio]]. You probably want to connect some [[Hardware]] to your computer to try and receive or transmit stuff. If you or your group would like to get a professional jump start on using GNU Radio and the USRP, [http://corganlabs.com Corgan Labs] offers a 3-day, hands-on training class that can be held at your own location.<br />
<br />
[[Category:Installation]]<br />
[[Category:Guide]]</div>Cmrincon