Tutorial - Using the QT GUI Blocks in GNU Radio
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:
- complex sink: qtgui.sink_c
- float sink: qtgui.sink_f
As this shows, the sinks are located in the module gnuradio.qtgui and is imported into Python with:
from gnuradio import qtgui
Both the complex and floating point versions of the sink take the same arguments using the following class constructor:
qtgui_make_sink_X (int fftsize, int wintype, double fc=0, double bandwidth=1.0, const std::string &name="Spectrum Display", bool plotfreq=true, bool plotwaterfall=true, bool plotwaterfall3d=true, bool plottime=true, bool plotconst=true, bool use_openGL=true, QWidget *parent=NULL)
The following describes the arguments meanings more:
- fftsize: initial FFT size
- 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
- fc: center frequency for the x-axis display
- bandwidth: sets the x-axis range around fc
- name: The title of the GUI object in the title bar
- plotfreq: Display frequency window?
- plotwaterfall: Display waterfall window?
- plotwaterfall3d: Display 3D waterfall window?
- plottime: Display time window?
- plotconst: Display constellation window?
- parent: a parent widget for this object to be put into as a child
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:
It is also recommended that you get PyQWT5 to be able to build more extensive and nicer looking GUIs.
Example 1: Seeing the GUI
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.
Here is the full code:
#!/usr/bin/env python3 from PyQt5 import Qt from gnuradio import gr from gnuradio import qtgui from gnuradio import analog from gnuradio import blocks from gnuradio.filter import firdes import sys, sip class my_tb(gr.top_block): def __init__(self): gr.top_block.__init__(self) # Make a local QtApp so we can start it from our side self.qapp = Qt.QApplication(sys.argv) samp_rate = 1e6 fftsize = 2048 self.src = analog.sig_source_c(samp_rate, analog.GR_SIN_WAVE, 0.1, 1, 0) self.nse = analog.noise_source_c(analog.GR_GAUSSIAN, 0.1) self.add = blocks.add_cc() self.thr = blocks.throttle(gr.sizeof_gr_complex, samp_rate, True) self.snk = qtgui.sink_c( fftsize, #fftsize firdes.WIN_BLACKMAN_hARRIS, #wintype 0, #fc samp_rate, #bw "", #name True, #plotfreq True, #plotwaterfall True, #plottime True, #plotconst ) self.connect(self.src, (self.add, 0)) self.connect(self.nse, (self.add, 1)) self.connect(self.add, self.thr, self.snk) # Tell the sink we want it displayed self.pyobj = sip.wrapinstance(self.snk.pyqwidget(), Qt.QWidget) self.pyobj.show() def main(): tb = my_tb() tb.start() tb.qapp.exec_() if __name__ == "__main__": try: main() except KeyboardInterrupt: pass
Lines 2 - 8 just set up the environment and modules, including the PyQT modules we require. Lines 10 - 12 simply set up the "my_tb" class as a GNU Radio top block class and initialize it.
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.
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.
Lines 37 - 39 just connect all of the blocks. Like any other block, the qtgui sink is added as just another connection.
Lines 42 and 43 are specific to making the qtgui blocks visible. We have to use the "show" 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 "pyqwidget" method of the qtgui sink passes the proper object pointer back to allow this handling. Now, the Python object has the "show()" method required to display the sink that is called in Line 43. Without this, the code would run, but we would see nothing displayed.
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 "my_tb" class. Instead of calling "run()" on this object, though, we only call "start()" 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 "exec_()" 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]
The big take-aways message here is the relatively minor changes required to make a qtgui application from a flow graph.
- Get a reference to the qApp pointer
- Build and connect the qtgui sink
- Use sip to get a wrapped instance in Python of the block and call "show()" on it
- Use the "start()" method of the top block class followed by a call to "exec_()" on the qApp to run the QT application