Coding Python GNU Radio Applications 对于GNURADIO编程参考API

http://gnuradio.org/redmine/projects/gnuradio/wiki/TutorialsWritePythonApplications#Coding-Python-GNU-Radio-Applications

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.

上面的例子已经覆盖了很多怎样使用Python 在GNU Radio的应用(dial_tone.py的例子,前面做了很多讲解,没贴上来)。这章下半部分主要讲解GNU Radio api和怎样使用它们。从现在开始,不需要把每章每个部分都看完,只需要对tiles就是标题有个熟悉的感觉,并且可以找出你想要知道什么就可以了。

GNU Radio Modules

GNU Radio 提供很多库和模块。你可以用下面语法包括这些模块

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

1 from gnuradio import MODULENAME

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

一些模块作用不同,下面是一些通用模块:

grThe main GNU Radio library. You will nearly always need this.GNU RADIO主要库函数,你会经常用到它

usrp

USRP sources and sinks and controls. USRP的 source和sink还有control模块
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. 声音控制。你可以用他们来发送和接收声音到声卡,你也可以用你的声卡和外部的RF前端来做窄带接收器

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.

这个模块包括了 额外增加的blocks。这些block是用python写的,像调制和解调,一些滤波器的代码,重抽样,噪声抑制等。

optfir

Routines for designing optimal FIR filters.

用来设计FIR滤波器的惯用模块

plot_data

Some functions to plot data with Matplotlib

一些用Matplotlib绘制数据的 functions

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.

这个常用子模块包括 为你的流图快速建立图形接口的工具。 用from gnuradio.wxgui import * 导入全部子模块或者用 from gnuradio.wxgui import stdgui2, fftsink2 导入一些自己需要的部分。可以从“Graphical User Interfaces”中找到更多信息

eng_notation

Adds some functions to deals with numbers in engineering notation such as @100M' for 100 * 10^6'.

增加一些处理工程上数字的表示函数,比如 @100M 表示 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).

这个模块用来扩展optparse的模块去明白工程表示

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.

以上的列表没有完全的完成,因为GNU Raadio的代码经常改变。所以很难去做一个固定的文档。

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.

上面的话的意思就是,如果你想用好GNU RADIO的东西,不如直接去看源码,因为里边的信息更多。可以找模块目录的PYTHON来使用或者GNU RADIO的源码目录。你应该注意gr-开头的源码目录。

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 (based on GNU Radio 3.1.1) which can be downloaded at

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

以上是一个非官方的GNU RADIO手册。

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:

1 src0 = gr.sig_source_f (sample_rate, gr.GR_SIN_WAVE, 350, ampl)
2 src1 = gr.sig_source_f (sample_rate, gr.GR_SIN_WAVE, 440, ampl)
3 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.

后缀经常用来做各种block的类型,比如 gr.filter_ccf()表示 FIR滤波器,复数输入,复数输出,和浮点tap

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:

1     # We're in some cool application here
2 
3         src0 = gr.sig_source_f (sample_rate, gr.GR_SIN_WAVE, 350, ampl)
4     # Other, fantastic things happen here
5 
6     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.

These are basic rules for connecting blocks and they work in most cases. However, when mixing data types some more notes are worth mentioning.

  • 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.
  • 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 packed_to_unpacked* and unpacked_to_packed* blocks for this.
  • 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.

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:

 1 class [[HierBlock]](gr.hier_block2):
 2     def __init__(self, audio_rate, if_rate):
 3         gr.hier_block2.__init__(self, "HierBlock",
 4                        gr.io_signature(1, 1, gr.sizeof_float),
 5                        gr.io_signature(1, 2, gr.sizeof_gr_complex))
 6 
 7         B1 = gr.block1(...) # Put in proper code here!
 8         B2 = gr.block2(...)
 9 
10         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. The '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_int
  • gr.sizeof_short
  • gr.sizeof_char

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:

 1 class FG1(gr.top_block):
 2     def __init__(self):
 3         gr.top_block.__init__(self)
 4 
 5     ... # Sources and other blocks are defined here
 6     other_block1 = gr.other_block()
 7     hierblock    = [[HierBlock]]()
 8     other_block2 = gr.other_block()
 9 
10     self.connect(other_block1, hierblock, other_block2)
11 
12     ... # 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:

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

 1 class transmit_path(gr.hier_block2):
 2     def __init__(self):
 3                 gr.hier_block2.__init__(self, "transmit_path",
 4                                 gr.io_signature(0, 0, 0), # Null signature
 5                                 gr.io_signature(0, 0, 0))
 6 
 7         source_block = gr.source()
 8         signal_proc  = gr.other_block()
 9         sink_block   = gr.sink()
1011         self.connect(source_block, signal_proc, sink_block)
12 
13 class receive_path(gr.hier_block2):
14     def __init__(self):
15                 gr.hier_block2.__init__(self, "receive_path",
16                                 gr.io_signature(0, 0, 0), # Null signature
17                                 gr.io_signature(0, 0, 0))
18 
19         source_block = gr.source()
20         signal_proc  = gr.other_block()
21         sink_block   = gr.sink()
22 
23         self.connect(source_block, signal_proc, sink_block)
24 
25 class my_top_block(gr.top_block):
26     def __init__(self):
27         gr.top_block.__init__(self)
28 
29     tx_path = transmit_path()
3031     rx_path = receive_path()
32 
33     self.connect(tx_path)
34     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.

See the documentation for gr_top_block for more details.

Example:

 1 class my_top_block(gr.top_block):
 2     def __init__(self):
 3         gr.top_block.__init__(self)
 4     ... # Define blocks etc. here
 5 
 6 if __name__ == '__main__':
 7     my_top_block().start()
 8     sleep(5) # Wait 5 secs (assuming sleep was imported!)
 9     my_top_block().stop()
10     my_top_block().wait() # If the graph is needed to run again, wait() must be called after stop
11     ... # Reconfigure the graph or modify it
12     my_top_block().start() # start it again
13     sleep(5) # Wait 5 secs (assuming sleep was imported!)
14     my_top_block().stop()  # since (assuming) the graph will not run again, no need for wait() to be called

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:

 1 class my_top_block(gr.top_block):
 2     def __init__(self):
 3         gr.top_block.__init__(self)
 4     ... # Define some blocks
 5     self.amp = gr.multiply_const_ff(1) # Define multiplier block
 6     ... # Define more blocks
 7 
 8         self.connect(..., self.amp, ...) # Connect all blocks
 9 
10     def set_volume(self, volume):
11     self.amp.set_k(volume)
12 
13 if __name__ == '__main__':
14     my_top_block().start()
15     sleep(2) # Wait 2 secs (assuming sleep was imported!)
16     my_top_block.set_volume(2) # Pump up the volume (by factor 2)
17     sleep(2) # Wait 2 secs (assuming sleep was imported!)
18     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.

http://www.scipy.org/

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:

 1     ... # We are inside some application
 2     tb = gr.top_block() # Define the container
 3 
 4     block1 = gr.some_other_block()
 5     block2 = gr.yet_another_block()
 6 
 7     tb.connect(block1, block2)
 8 
 9     ... # The application does some wonderful things here
1011     tb.start() # Start the flow graph
12 
13     ... # 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_floatLike the original float option, but also accepts engineering notation like 101.8M
subdevOnly accepts valid subdevice descriptors such as A:0 (To specify a daughterboard on a USRP)
intxOnly 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.

Nearly every GNU Radio example uses this feature. Try dial_tone.py for an easy example.

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:

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

stdgui2Basic GUI stuff, you always need this
fftsink2Plot FFTs of your data to create spectrum analyzers or whatever
scopesink2Oscilloscope output
waterfallsink2Waterfall output
numbersink2Displays numerical values of incoming data
formOften 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:

1 class my_gui_flow graph(stdgui2.std_top_block):
2         def __init__(self, frame, panel, vbox, argv):
3                 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):

frameThe wx.Frame of your window. You can get at the predefined menu by using frame.GetMenuBar()
panelA panel, placed in @frame', to hold all your wxControl widgets
vboxA vertical box sizer (wx.BoxSizer(wx.VERTICAL) is how it is defined), used to align your widgets in the panel
argvThe 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:

 1 from gnuradio.wxgui import stdgui2, fftsink2
 2 
 3 # App gets defined here ...
 4 
 5 # FFT display (pseudo-spectrum analyzer)
 6 my_fft = fftsink2.fft_sink_f(panel, title="FFT of some Signal", fft_size=512,
 7              sample_rate=sample_rate, ref_level=0, y_per_div=20)
 8 self.connect(source_block, my_fft)
 9 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:

1 if __name__ == '__main__':
2     app = stdgui2.stdapp(my_gui_flow_graph, "GUI GNU Radio Application")
3     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.