Version 1/24 - Next » - Current version
Guest User, 07/01/2008 05:23 pm
Added the Tutorial


= Introduction =

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.

This is a tutorial on how to write applications for GNU Radio in Python. 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.

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.

= Preliminaries =

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 or audio) is helpful, although not strictly required. If the GNU Radio examples work (such as dial_tone.py in gnuradio-examples/python/audio), you're ready to go.

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.

= Understanding flow graphs =

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.

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).

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.

Examples
In order to illuminate this diffuse topic a little, let's start with some examples:

Low-pass filtered audio recorder: {{{
--- --- -------------- | Mic - LPF - Record to file |
--- --- --------------
}}}

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.

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.

Dial tone generator: {{{
---------------------- | Sine generator (350Hz) --
----------------------- | ----------
--- | | Audio sink |
-- |
----------------------- | ---------- | Sine generator (440Hz) --
-----------------------
}}}

This simple example is often called the "Hello World of GNU Radio". 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 gnuradio-examples/python/audio/dial_tone.py.

QPSK Demodulator: {{{
----------- -------------- ---------------- | USRP Source - Frequency sync - Matched filter |
----------- -------------- ---------------+ | COMPLEX SAMPLES
-----------------+ | Symbol demodulator |
-----------------+ | COMPLEX SYMBOLS
--------------- --------------- ----------+ | Source decoder - Channel decoder - Bit mapping |
--------------+ --------------- ----------- | BITS
--------------+ | Application | DATA
---------------
}}}

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.

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.

Walkie Talkie: {{{
------------ ---------------- ------- ---------- | USRP Source - NBFM Demodulator - Squelch - Audio Sink |
------------- ---------------- ------- ----------
------------ -------------- ---------- | Audio Source -------- NBFM Modulator ------- USRP Sink |
------------ -------------- ----------
}}}

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 gnuradio-examples/python/usrp/usrp_nbfm_ptt.py.

Summary
This concludes the chapter about flow graphs. Here's a quick summary about the most vital points you really need to know:
  • All signal processing in GNU Radio is done through flow graphs.
  • 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.
  • Data passes between blocks in various formats, complex or real integers,
    floats or basically any kind of data type you can define.
  • Every flow graph needs at least one sink and source.

= A FIRST WORKING CODE EXAMPLE =
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.

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 gnuradio-examples/python/audio/dial_tone.py.

{{{
#!python
#!/usr/bin/env python

from gnuradio import gr
from gnuradio import audio

class my_top_block(gr.top_block):
def init(self):
gr.top_block.__init__(self)

sample_rate = 32000
ampl = 0.1
src0 = gr.sig_source_f (sample_rate, gr.GR_SIN_WAVE, 350, ampl)
src1 = gr.sig_source_f (sample_rate, gr.GR_SIN_WAVE, 440, ampl)
dst = audio.sink (sample_rate, "")
self.connect (src0, (dst, 0))
self.connect (src1, (dst, 1))

if name '__main__':
try:
my_top_block().run()
except KeyboardInterrupt:
pass
}}}

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.

Lines 3 and 4 import necessary Python modules to run GNU Radio. The `import` command is similar to the `#include` directive in C/C++. Here, two modules from the gnuradio-package are imported: `gr` and `audio`. The first module, `gr`, is the basic GNU Radio module. You will always have to import this to run a GNU Radio application. The second loads audio device blocks. There are many GNU Radio modules, a short list of modules will be presented later on.

Lines 6-17 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`, you get all the hooks and functions you need to add blocks and connect them.

Only one member function is defined for this class: the function `__init__()`, 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).
Next, two variables are defined: `sample_rate` and `ampl`. These will control sampling rate and amplitude of the signal generators.

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 signal sources are generated (called `src0` and `src1`). 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 "f" of the block type `gr.sig_source_f` indicates the output is of type `float`, 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 `audio.sink`, 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.
The signal sink is defined in line 15: `audio.sink()` returns a block which acts as a 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.

Lines 16 and 17 connect the blocks. 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. You can connect as many blocks as you wish with one `connect()` call. Here, a special syntax is necessary because we want to connect `src0` with the first input of `dst` and `src1` with the second one. `self.connect (src0, (dst, 0))` does exactly this: it specifically connects `src0` to port 0 of `dst`. `(dst, 0)` is called a "tuple" in Python jargon. In the `self.connect()` 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

{{{
#!python
self.connect((src0, 0), (dst, 0))
}}}

That's all there is to create a flow graph. The last 5 lines do nothing but start the flow graph (line 22). The `try` and `except` statements simply make sure the flow graph (which would otherwise run infinitely) are stopped when Ctrl+C is pressed (which triggers a `KeyboardInterrupt` Python exception).

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 `run()` method on the instance(es).

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.
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 .

Summary ==
  • You need to import required GNU Radio modules with the `from gnuradio import` command. You always need the module `gr`.
  • A flow graph is contained in a class which itself is derived from `gr.top_block`.
  • Blocks are created by calling functions such as `gr.sig_source_f()` and saving the return value to a variable.
  • Blocks are connected by calling `self.connect()` from within the flow graph class
  • If you don't feel comfortable writing some basic Python code now, have a break and go through some Python tutorials.

The next section will give a more detailed overview about writing GNU Radio applications in Python.

= Coding Python GNU Radio Applications =

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 possibilites 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.

GNU Radio Modules

GNU Radio comes with quite a lot of libraries and modules. You will usually include modules with the following syntax:

{{{
#!python
from gnuradio import MODULENAME
}}}

Some modules work a bit differently, see the following list on the most common modules.

gr The main GNU Radio library. You will nearly always need this.
usrp USRP sources and sinks and controls.
audio 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.
blks2 This module contains additional blocks written in Python which include often-used tasks like modulators and demodulators, some extra filter code, resamplers, squelch and so on.
optfir Routines for designing optimal FIR filters.
wxgui This is actually a submodule, containing utilities to quickly create graphical user interfaces to your flow graphs. Use `from gnuradio.wxgui import *` to import everything in the submodule or `from gnuradio.wxgui import stdgui2, fftsink2` to import specific components. See the section 'Graphical User Interfaces' for more information.
eng_notation Adds some functions to deals with numbers in engineering notation such as `100M' for 100 * 10^6'.
eng_options Use `from gnuradio.eng_options import eng_options` to import this feature. This module extends Pythons `optparse` module to understand engineering notation (see above).
gru Miscellaneous utilities, mathematical and others.

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.

Instead, you will have to use the good old Star Wars motto to delve further into the details of the modules: "Use the source!". 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 `gr-` in the source directory, such as gr-sounder or gr-radar-mono. These produce their own code and, consequently, their own modules.

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.

Choosing, defining and configuring blocks

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.

As with modules, the GNU Radio code changes around quite a bit, so a static documentation doesn't really make sense. However, the situation is a bit different with blocks. First of all, there's the unofficial GNU Radio manual which can be downloaded at

http://rapidshare.com/files/72169239/Simple-Gnuradio-User-Manual-v1.0.pdf

Thanks to the author for the massive effort!

Then, there's the automatically generated documentation. This is created by Doxygen from the source code. I recommend making these docs the same time as building the rest. Run `./configure` with the `--enable-doxygen` switch for to create the docs when running `make`. The latter is particularly useful as it consists of multiple HTML documents which can be browsed easily. If you don't want to create the documents yourself or don't have to possibility to do so, you can find a version (although not an up-to-date one) on the web at http://gnuradio.org/doc/doxygen/hierarchy.html. The auto-generated docs are in your source directory beneath gnuradio-core/doc/html.
Learning how to use these documentations is a major part of learning how to use GNU Radio!

Let's get practical. Here's the three lines from the previous example which define the blocks:

{{{
#!python
src0 = gr.sig_source_f (sample_rate, gr.GR_SIN_WAVE, 350, ampl)
src1 = gr.sig_source_f (sample_rate, gr.GR_SIN_WAVE, 440, ampl)
dst = audio.sink (sample_rate, "")
}}}

Here's a simplified version of what happens when this code is executed: First, a function called `sig_source_f` in the module `gr` is executed. It receives four function arguments:

  • sample_rate, which is a Python variable,
  • gr.GR_SIN_WAVE, which is a constant defined in the `gr' module,
  • 350, a normal literal constant,
  • ampl, another variable.

This function creates a class which is subsequently assigned to `src0`. The same happens on the other two lines, although the sink is fetched from a different module (`audio`).

So how did I know which block to use and what to pass to `gr.sig_source_f()`? This is where the documentation comes in. If you use the Doxygen-generated docs, click on the top left tab called "Modules". Proceed to "Signal Sources". You will find a list of signal generators, including the `sig_source_*` family. The suffix defines the data type at the output:

  • f = float
  • c = complex float
  • i = int
  • s = short int
  • b = bits (actually an integer type)

These suffixes are used for all types of blocks, e.g. `gr.fir_filter_ccf()` will define an FIR filter with complex input, complex output and float taps, and `gr.add_const_ss()` will define a block which adds incoming short values with another, constant, short int.

A list of all classes with a short description can be obtained by clicking on the top tab called "Classes". The unofficial GNU Radio manual lists all classes sorted by module and can be searched using your PDF reader of choice.
Most blocks you'll be using at the beginning are either from the `gr`, `audio` or `usrp` modules. So if you find a class called `gr_sig_source_f` in the auto-generated docs, you can create this class in Python by calling `gr.sig_source_f()`.

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 SWIG to create an interface between Python and C++. Every block in C++ comes with a creating function, called `gr_make_***` (`gr_make_sig_source_f()` 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 `gr.sig_source_f()` in Python calls `gr_make_sig_source_f()` in C++. For the same reason, it takes the same arguments - that's how you know how to initialise a block in Python.

Once you're browsing the Doxygen documentation of the class `gr_sig_source_f`, you might notice many other class methods, such as `set_frequency()`. 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:

{{{
#!python # We're in some cool application here

src0 = gr.sig_source_f (sample_rate, gr.GR_SIN_WAVE, 350, ampl)
  1. Other, fantastic things happen here
src0.set_frequency(880) # Change frequency
}}}

will change the frequency of the first signal generator to 880Hz.

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.

Connecting blocks
Use the `connect()` method of `gr.top_block` to connect blocks. Some things are worth mentioning:
  • 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.
  • One output can be connected to several inputs; you don't need an extra block
    to duplicate signal paths.
Hierarchical blocks

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.

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 HierBlock:

{{{
----------------- | --- -- |
--+--+ B1 - B2 ---- | --- -- | | HierBlock |
-----------------
}}}

This is what you do: create a flow graph which derives from `gr.hier_block2` and use `self` as source and sink:

{{{
#!python
class HierBlock(gr.hier_block2):
def init(self, audio_rate, if_rate):
gr.hier_block2.__init__(self, "HierBlock",
gr.io_signature(1, 1, gr.sizeof_float),
gr.io_signature(1, 2, gr.sizeof_gr_complex))

B1 = gr.block1(...) # Put in proper code here!
B2 = gr.block2(...)
self.connect(self, B1, B2, self)
}}}

As you can see, creating a hierarchical block is very similar to creating a flow graph with `gr.top_block`. Apart from using `self` 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 `gr.hier_block2.__init__()` takes four parameters:

  • self (which is always passed to the constructor as first argument),
  • a string with an identifier for the hierarchical block (change at your convenience),
  • an input signature and an
  • output signature.

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 `gr.io_signature()`, as is done here. This function call takes 3 arguments:

  • minimum number of ports,
  • maximum number of ports and
  • size of the input/output elements.

For the hierarchical block `HierBlock`, you can see that it has exactly one input and one or two outputs. The incoming objects are of size `float`, 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 `gr.sizeof_gr_complex`. `gr.sizeof_float` and `gr.sizeof_gr_complex` are equivalent to the C++ return values of the `sizeof()` call. Other predefined constants are

  • gr.sizeof_char,
  • gr.sizeof_in and
  • gr.sizeof_short.

Use gr.io_signature(0, 0, 0) to create a null IO signature, i.e. for defining hierarchical blocks as sources or sinks.

That's all. You can now use `HierBlock` as you would use a regular block. For example, you could put this code in the same file:

{{{
#!python
class FG1:
def init(self):
gr.top_block.__init__(self)

... # Sources and other blocks are defined here
other_block1 = gr.other_block()
hierblock = HierBlock()
other_block2 = gr.other_block()
self.connect(other_block1, hierblock, other_block2)
... # Define rest of FG1
}}}

Of course, to make use of Pythons modularity, you could also put the code for `HierBlock` in an extra file called `hier_block.py`. To use this block from another file, simply add an import directive to your code:

{{{
#!python
from hier_block import HierBlock
}}}

and you can use `HierBlock` as mentioned above.

Examples for hierarchical blocks:

{{{
gnuradio-examples/python/usrp/fm_tx4.py
gnuradio-examples/python/usrp/fm_tx_2_daughterboards.py
gnuradio-examples/python/digital/tx_voice.py
}}}

Multiple flow graphs

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 `gr.hier_block2` like in the section above. Then, create a top_block to hold the flow graphs.

Example:

{{{
#!python
class transmit_path(gr.hier_block2):
def init(self):
gr.hier_block2.__init__(self, "transmit_path",
gr.io_signature(0, 0, 0), # Null signature
gr.io_signature(0, 0, 0))

source_block = gr.source()
signal_proc = gr.other_block()
sink_block = gr.sink()
self.connect(source_block, signal_proc, sink_block)

class receive_path(gr.hier_block2):
def init(self):
gr.hier_block2.__init__(self, "receive_path",
gr.io_signature(0, 0, 0), # Null signature
gr.io_signature(0, 0, 0))

source_block = gr.source()
signal_proc = gr.other_block()
sink_block = gr.sink()
self.connect(source_block, signal_proc, sink_block)

class my_top_block(gr.top_block):
def init(self):
gr.top_block.__init__(self)

tx_path = transmit_path()
rx_path = receive_path()
self.connect(tx_path)
self.connect(rx_path)
}}}

Now, when you start `my_top_block`, 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 `self` 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.

Examples for multiple flow graphs:

{{{
gnuradio-examples/python/usrp/usrp_nbfm_ptt.py
}}}

GNU Radio extensions and tools

GNU Radio is more than blocks and flow graphs - it comes with a lot of tools and code to help you write DSP applications.

A collection of useful GNU Radio applications designed to aid you is in gr-utils/.
Browse the source code in gnuradio-core/src/python/gnuradio to find utilities you can use in your Python code such as filter design code, modulation utilities and more.

Controlling flow graphs

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 `gr.top_block`. The question remains on how to control one of these classes.

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:

`run()` 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.
`start()` Start the contained flow graph. Returns to the caller once the
threads are created.
`stop()` Stop the running flow graph. Notifies each thread created by the
scheduler to shutdown, then returns to caller.
`wait()` 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.
`lock()` Lock a flow graph in preparation for reconfiguration.
`unlock()` 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.
`is_running()` Returns true if flow graph is running.

Example:

{{{
#!python
class my_top_block(gr.top_block):
def init(self):
gr.top_block.__init__(self)
... # Define blocks etc. here

if name == '__main__':
my_top_block().start()
sleep(5) # Wait 5 secs (assuming sleep was imported!)
my_top_block().stop()
sleep(1) # Give the threads time to really shut down
print my_top_block().is_running() # Should return False
}}}

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 `gr.multiply_const_ff`. If you check the documentation for this kind of of block, you will find a function `gr.multiply_const_ff.set_k()` which sets the multiplication factor.
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.

Example:

{{{
#!python
class my_top_block(gr.top_block):
def init(self):
gr.top_block.__init__(self)
... # Define some blocks
self.amp = gr.multiply_const_ff(1) # Define multiplier block
... # Define more blocks

self.connect(..., self.amp, ...) # Connect all blocks
def set_volume(self, volume):
self.amp.set_k(volume)

if name '__main__':
my_top_block().start()
sleep(2) # Wait 2 secs (assuming sleep was imported!)
my_top_block.set_volume(2) # Pump up the volume (by factor 2)
sleep(2) # Wait 2 secs (assuming sleep was imported!)
my_top_block().stop()
}}}

This example runs the flow graph for 2 seconds and then doubles the volume by accessing the `amp` block through a member function called `set_volume()`. Of course, one could have accessed the `amp` attribute directly, omitting the member function.

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.

Non-flow graph centred applications ==

Up until now, GNU Radio applications in this tutorial have always been centred around the one class derived from gr.top_block. 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.

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.

= Advanced Topics =

If you have really read the previous sections, you already know enough to write your first Python GNU Radio applications. This section will adress some slightly more advanced functionalities for Python GNU Radio applications.

Dynamic flow graph creation

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.
This can be achieved by taking the code out of the `__init__()` function and simply using `gr.top_block` as a container. Example:

{{{
#!python
... # We are inside some application
tb = gr.top_block() # Define the container

block1 = gr.some_other_block()
block2 = gr.yet_another_block()
tb.connect(block1, block2)
... # The application does some wonderful things here
tb.start() # Start the flow graph
... # Do some more incredible and fascinating stuff here
}}}

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.

Examples for this kind of flow graph setup:

{{{
gnuradio-examples/python/apps/hf_explorer/hfx2.py
}}}

Command Line Options

Python has its own libraries to parse command line options. See the documentation for the module `optparse` to find out how to use it.

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:

eng_float Like the original float option, but also accepts engineering notation like 101.8M
subdev Only accepts valid subdevice descriptors such as A:0 (To specify a daughterboard on a USRP)
intx Only accepts integers

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.

Graphical User Interfaces

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.

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.

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/).

To use the GNU Radio wxWidgets tools, you need to import some modules:

{{{
#!python
from gnuradio.wxgui import stdgui2, fftsink2, slider, form
}}}

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/src/python).

stdgui2 Basic GUI stuff, you always need this
fftsink2 Plot FFTs of your data to create spectrum analyzers or whatever
scopesink2 Oscilloscope output
waterfallsink2 Waterfall output
numbersink2 Displays numerical values of incoming data
form Often used input forms (see below)

Next, we have to define a new flow graph. This time, we don't derive from `gr.top_block` but from `stdgui2.std_top_block`:

{{{
#!python
class my_gui_flow graph(stdgui2.std_top_block):
def init(self, frame, panel, vbox, argv):
stdgui2.std_top_block.__init__ (self, frame, panel, vbox, argv)
}}}

As you can see, there's another difference: the constructor gets a couple of new parameters. This is because a `stdgui2.std_top_block` 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):

frame The wx.Frame of your window. You can get at the predefined menu by using frame.GetMenuBar()
panel A panel, placed in `frame', to hold all your wxControl widgets
vbox A vertical box sizer (wx.BoxSizer(wx.VERTICAL) is how it is defined), used to align your widgets in the panel
argv The command line arguments

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 `form`.

form has a great number of input widgets: `form.static_text_field()` for static text field (display only), `form.float_field()`, to input float values, `form.text_field()` to input text, `form.checkbox_field()` for checkboxes, `form.radiobox_field()` for radioboxes etc. Check the source code of gr-wxgui/src/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.
See one of the examples mentioned below on how to add widgets using form.

Probably the most useful part of `gnuradio.wxgui` is the possibility to directly plot incoming data. To do this, you need one of the sinks that come with `gnuradio.wxgui`, such as `fftsink2`. These sinks work just as any other GNU Radio sink, but also have properties needed for use with wxPython. Example:

{{{
#!python
from gnuradio.wxgui import stdgui2, fftsink2

  1. App gets defined here ...
  1. FFT display (pseudo-spectrum analyzer)
    my_fft = fftsink2.fft_sink_f(panel, title="FFT of some Signal", fft_size=512,
    sample_rate=sample_rate, ref_level=0, y_per_div=20)
    self.connect(source_block, my_fft)
    vbox.Add(my_fft.win, 1, wx.EXPAND)
    }}}

First, the block is defined (`fftsink2.fft_sink_f`). Apart from typical DSP parameters such as the sampling rate, it also needs the `panel` object which is passed to the constructor. Next, the block is connected to a source. Finally, the FFT window (`my_fft.win`) is placed inside the `vbox` BoxSizer to actually display it. Remember that a signal block output can be connected to any amount of inputs.

Finally, the whole thing needs to be started. Because we need an `wx.App()` to run the GUI, the startup code is a bit different from a regular flow graph:

{{{
#!python
if name == '__main__':
app = stdgui2.stdapp(my_gui_flow_graph, "GUI GNU Radio Application")
app.MainLoop()
}}}

`stdgui2.stdapp()` creates the `wx.App` with `my_gui_flow_graph` (the first argument). The window title is set to "GUI GNU Radio Application".

Examples for simple GNU Radio GUIs:

{{{
gr-utils/src/python/usrp_fft.py
gr-utils/src/python/usrp_oscope.py
gnuradio-examples/python/audio/audio_fft.py
gnuradio-examples/python/usrp/usrp_am_mw_rcv.py
}}}

And many more.

= WHAT NEXT? =

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:

  • Use the source. Especially the examples in gnuradio-examples/ and gr-utils/ can be very helpful.
  • Check the mailing list archives. Chances are very high your problem has been asked before.