Flowgraph Python Code: Difference between revisions

From GNU Radio
Jump to navigation Jump to search
(initial creation)
 
(7 intermediate revisions by 4 users not shown)
Line 1: Line 1:
== Dial Tone Flowgraph  ==
== Dial Tone Flowgraph  ==


The following is a dial-tone example using gnuradio-companion (GRC).
The following example flowgraph implements a dial-tone:


https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png
https://raw.githubusercontent.com/gnuradio/gr-tutorial/master/examples/tutorial3/images/tutorial_three_1.png


When we click the '''Generate''' button, the terminal tells us it produced a .py file, so let's open that to examine its code.
When we click the '''Generate''' button from within GRC, the terminal tells us that it produced a "tutorial_three_1.py" file, so let's open it to examine the code.


<syntaxhighlight lang="python" line="line">
<syntaxhighlight lang="python" line="line">
Line 144: Line 144:


Once GRC has created a Python file, the user is free to modify it in any desired manner, such as changing parameters, sample rate, and even connections among the blocks.
Once GRC has created a Python file, the user is free to modify it in any desired manner, such as changing parameters, sample rate, and even connections among the blocks.
<p><b>Warning: </b>After the Python file is modified, running GRC again with that flowgraph will wipe out your changes!</p>


== Dial Tone Python code Dissected ==
To execute this file from a terminal, enter:<br>
<code>python3 tutorial_three_1.py</code>


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.
<p><b>Warning: </b>After the Python file has been modified, running GRC again with that flowgraph will wipe out your changes!</p>


<syntaxhighlight lang="python" line="line">
== Dial Tone Python Code Dissected ==
#!/usr/bin/env Python3
from gnuradio import gr
from gnuradio import audio
from gnuradio import analog
 
class my_top_block(gr.top_block):
    def __init__(self):
        gr.top_block.__init__(self)
 
        sample_rate = 32000
        ampl = 0.1


        src0 = analog.sig_source_f(sample_rate, analog.GR_SIN_WAVE, 350, ampl)
Let's examine pertinent lines of the code:
        src1 = analog.sig_source_f(sample_rate, analog.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
 
</syntaxhighlight>
 
Let us examine this line by line:


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
#!/usr/bin/env python3
#!/usr/bin/env python3
</syntaxhighlight>
</syntaxhighlight>
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.
 
This tells the shell to use the Python3 interpreter to run this file.


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
from gnuradio import analog
from gnuradio import audio
from gnuradio import gr
from gnuradio import gr
from gnuradio import audio
from gnuradio import analog
</syntaxhighlight>
</syntaxhighlight>


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.
These tell Python what 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 is included in the analog module.


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
class my_top_block(gr.top_block):
class tutorial_three_1(gr.top_block, Qt.QWidget):
</syntaxhighlight>
</syntaxhighlight>
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.
 
Define a class called "tutorial_three_1" 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.


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
def __init__(self):
def __init__(self):
</syntaxhighlight>
</syntaxhighlight>
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.
Only one member function is defined for this class: the function "''init''()", which is the constructor of this class.


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
gr.top_block.__init__(self)
gr.top_block.__init__(self, "tutorial_three_1")
</syntaxhighlight>
</syntaxhighlight>
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).
 
The parent constructor is called.


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
sample_rate = 32000
self.samp_rate = samp_rate = 32000
ampl = 0.1
</syntaxhighlight>
</syntaxhighlight>
Variable declarations for sampling rate and amplitude that we will later use.


==Connecting the Blocks ==
Variable declaration for sample rate.


<syntaxhighlight lang="python">
<syntaxhighlight lang="python">
self.connect(src0, (dst, 0))
self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))
self.connect(src1, (dst, 1))
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))
</syntaxhighlight>
</syntaxhighlight>
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.


<syntaxhighlight lang="python">
There are 2 inputs to the '''Audio Sink''' block. The first line connects the only output of analog_sig_source_x_0 (440 Hz waveform) to the first input of audio_sink_0. The second line connects the only output of analog_sig_source_x_1 (350 Hz waveform) to the second input of audio_sink_0.
if __name__ == '__main__':
 
    try:
== GNU Radio Flowgraph in Lisp ==
         my_top_block().run()
 
    except KeyboardInterrupt:
Can this be done in Lisp?
        pass
 
Yes, if the Lisp is [http://hylang.org/ Hy] - which has very tight coupling to Python.
 
<syntaxhighlight lang="lisp">
(import [gnuradio [gr]]
[gnuradio [audio]]
[gnuradio.eng_arg [eng_float]]
[gnuradio [analog]])
 
(defclass my_top_block [gr.top_block]
"Play a dialtone through the speakers"
    (defn __init__[self]
        (.__init__ gr.top_block self)
(setv args
      (parse-args [["-O" "--audio-output" :default ""
              :help "pcm output device name.  E.g., hw:0,0 or /dev/dsp"]
          ["-r" "--sample-rate" :type eng_float :default 48000
              :help "set sample rate, default=%(default)s"]]
          :description "Set sound card and sample rate"))
        (setv sample_rate args.sample_rate)
        (setv ampl 0.1)
        (setv src0 (analog.sig_source_f sample_rate analog.GR_SIN_WAVE 350 ampl)
              src1 (analog.sig_source_f sample_rate analog.GR_SIN_WAVE 440 ampl))
        (setv dst (audio.sink sample_rate args.audio_output))
        (.connect self src0 [dst 0])
         (.connect self src1 [dst 1])))
 
(defmain [&rest args]
(try
  (.run (my_top_block))
  (except [KeyboardInterrupt])))
</syntaxhighlight>
</syntaxhighlight>
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.

Latest revision as of 03:53, 27 September 2021

Dial Tone Flowgraph

The following example flowgraph implements a dial-tone:

tutorial_three_1.png

When we click the Generate button from within GRC, the terminal tells us that it produced a "tutorial_three_1.py" file, so let's open it to examine the code.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#
# SPDX-License-Identifier: GPL-3.0
#
# GNU Radio Python Flow Graph
# Title: tutorial_three_1
# GNU Radio version: 3.8.0.0

from distutils.version import StrictVersion

if __name__ == '__main__':
    import ctypes
    import sys
    if sys.platform.startswith('linux'):
        try:
            x11 = ctypes.cdll.LoadLibrary('libX11.so')
            x11.XInitThreads()
        except:
            print("Warning: failed to XInitThreads()")

from gnuradio import analog
from gnuradio import audio
from gnuradio import gr
from gnuradio.filter import firdes
import sys
import signal
from PyQt5 import Qt
from argparse import ArgumentParser
from gnuradio.eng_arg import eng_float, intx
from gnuradio import eng_notation
from gnuradio import qtgui

class tutorial_three_1(gr.top_block, Qt.QWidget):

    def __init__(self):
        gr.top_block.__init__(self, "tutorial_three_1")
        Qt.QWidget.__init__(self)
        self.setWindowTitle("tutorial_three_1")
        qtgui.util.check_set_qss()
        try:
            self.setWindowIcon(Qt.QIcon.fromTheme('gnuradio-grc'))
        except:
            pass
        self.top_scroll_layout = Qt.QVBoxLayout()
        self.setLayout(self.top_scroll_layout)
        self.top_scroll = Qt.QScrollArea()
        self.top_scroll.setFrameStyle(Qt.QFrame.NoFrame)
        self.top_scroll_layout.addWidget(self.top_scroll)
        self.top_scroll.setWidgetResizable(True)
        self.top_widget = Qt.QWidget()
        self.top_scroll.setWidget(self.top_widget)
        self.top_layout = Qt.QVBoxLayout(self.top_widget)
        self.top_grid_layout = Qt.QGridLayout()
        self.top_layout.addLayout(self.top_grid_layout)

        self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")

        try:
            if StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):
                self.restoreGeometry(self.settings.value("geometry").toByteArray())
            else:
                self.restoreGeometry(self.settings.value("geometry"))
        except:
            pass

        ##################################################
        # Variables
        ##################################################
        self.samp_rate = samp_rate = 32000

        ##################################################
        # Blocks
        ##################################################
        self.audio_sink_0 = audio.sink(samp_rate, '', True)
        self.analog_sig_source_x_1 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 350, 0.1, 0, 0)
        self.analog_sig_source_x_0 = analog.sig_source_f(samp_rate, analog.GR_COS_WAVE, 440, 0.1, 0, 0)



        ##################################################
        # Connections
        ##################################################
        self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))
        self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))

    def closeEvent(self, event):
        self.settings = Qt.QSettings("GNU Radio", "tutorial_three_1")
        self.settings.setValue("geometry", self.saveGeometry())
        event.accept()

    def get_samp_rate(self):
        return self.samp_rate

    def set_samp_rate(self, samp_rate):
        self.samp_rate = samp_rate
        self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate)
        self.analog_sig_source_x_1.set_sampling_freq(self.samp_rate)



def main(top_block_cls=tutorial_three_1, options=None):

    if StrictVersion("4.5.0") <= StrictVersion(Qt.qVersion()) < StrictVersion("5.0.0"):
        style = gr.prefs().get_string('qtgui', 'style', 'raster')
        Qt.QApplication.setGraphicsSystem(style)
    qapp = Qt.QApplication(sys.argv)

    tb = top_block_cls()
    tb.start()
    tb.show()

    def sig_handler(sig=None, frame=None):
        Qt.QApplication.quit()

    signal.signal(signal.SIGINT, sig_handler)
    signal.signal(signal.SIGTERM, sig_handler)

    timer = Qt.QTimer()
    timer.start(500)
    timer.timeout.connect(lambda: None)

    def quitting():
        tb.stop()
        tb.wait()
    qapp.aboutToQuit.connect(quitting)
    qapp.exec_()


if __name__ == '__main__':
    main()

Once GRC has created a Python file, the user is free to modify it in any desired manner, such as changing parameters, sample rate, and even connections among the blocks.

To execute this file from a terminal, enter:
python3 tutorial_three_1.py

Warning: After the Python file has been modified, running GRC again with that flowgraph will wipe out your changes!

Dial Tone Python Code Dissected

Let's examine pertinent lines of the code:

#!/usr/bin/env python3

This tells the shell to use the Python3 interpreter to run this file.

from gnuradio import analog
from gnuradio import audio
from gnuradio import gr

These tell Python what 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 is included in the analog module.

class tutorial_three_1(gr.top_block, Qt.QWidget):

Define a class called "tutorial_three_1" 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.

def __init__(self):

Only one member function is defined for this class: the function "init()", which is the constructor of this class.

gr.top_block.__init__(self, "tutorial_three_1")

The parent constructor is called.

self.samp_rate = samp_rate = 32000

Variable declaration for sample rate.

self.connect((self.analog_sig_source_x_0, 0), (self.audio_sink_0, 0))
self.connect((self.analog_sig_source_x_1, 0), (self.audio_sink_0, 1))

There are 2 inputs to the Audio Sink block. The first line connects the only output of analog_sig_source_x_0 (440 Hz waveform) to the first input of audio_sink_0. The second line connects the only output of analog_sig_source_x_1 (350 Hz waveform) to the second input of audio_sink_0.

GNU Radio Flowgraph in Lisp

Can this be done in Lisp?

Yes, if the Lisp is Hy - which has very tight coupling to Python.

(import [gnuradio [gr]]
	[gnuradio [audio]]
	[gnuradio.eng_arg [eng_float]]
	[gnuradio [analog]])

(defclass my_top_block [gr.top_block]
 "Play a dialtone through the speakers"
    (defn __init__[self]
        (.__init__ gr.top_block self)
	(setv args
	      (parse-args [["-O" "--audio-output" :default ""
	        	      :help "pcm output device name.  E.g., hw:0,0 or /dev/dsp"]
		           ["-r" "--sample-rate" :type eng_float :default 48000
		              :help "set sample rate, default=%(default)s"]]
		          :description "Set sound card and sample rate"))
        (setv sample_rate args.sample_rate)
        (setv ampl 0.1)
        (setv src0 (analog.sig_source_f sample_rate analog.GR_SIN_WAVE 350 ampl)
              src1 (analog.sig_source_f sample_rate analog.GR_SIN_WAVE 440 ampl))
        (setv dst (audio.sink sample_rate args.audio_output))
        (.connect self src0 [dst 0])
        (.connect self src1 [dst 1])))

(defmain [&rest args]
 (try 
  (.run (my_top_block))
  (except [KeyboardInterrupt])))