Where Do Blocks Come From? Official tutorial for adding GNU Radio blocks in Python

This article is translated from https://wiki.gnuradio.org/index.php/Guided_Tutorial_GNU_Radio_in_Python#3.2._Where_Do_Blocks_Come_From.3F

1. Use gr_modtool

Before we start, we need to understand what gr_modtool's commands are, so let's take a look at the help.

$ gr_modtool help
Usage:
gr_modtool  [options] -- Run  with the given options.
gr_modtool help -- Show a list of commands.
gr_modtool help  -- Shows the help for a given command.

List of possible commands:

Name      Aliases          Description
=====================================================================
disable   dis              Disable block (comments out CMake entries for files)
info      getinfo,inf      Return information about a given module
remove    rm,del           Remove block (delete files and remove Makefile entries)
makexml   mx               Make XML file for GRC block bindings
add       insert           Add block to the out-of-tree module.
newmod    nm,create        Create a new out-of-tree module

We will see that there are many commands available here. In this tutorial we are only concerned with newmod and add; however, other detailed explanations give you the flexibility to use other gr_modtool commands without guidance.
First, we noticed that we can also request more information about a single command. Let's start with newmod, as this is the command used to create a new out-of-tree module.

$ gr_modtool help newmod
Usage: gr_modtool nm [options].
 Call gr_modtool without any options to run it interactively.

Options:
  General options:
    -h, --help          Displays this help message.
    -d DIRECTORY, --directory=DIRECTORY
                        Base directory of the module. Defaults to the cwd.
    -n MODULE_NAME, --module-name=MODULE_NAME
                        Use this to override the current module's name (is
                        normally autodetected).
    -N BLOCK_NAME, --block-name=BLOCK_NAME
                        Name of the block, where applicable.
    --skip-lib          Don't do anything in the lib/ subdirectory.
    --skip-swig         Don't do anything in the swig/ subdirectory.
    --skip-Python       Don't do anything in the Python/ subdirectory.
    --skip-grc          Don't do anything in the grc/ subdirectory.
    --scm-mode=SCM_MODE
                        Use source control management (yes, no or auto).
    -y, --yes           Answer all questions with 'yes'. This can overwrite
                        and delete your files, so be careful.

  New out-of-tree module options:
    --srcdir=SRCDIR     Source directory for the module template

Now that we can read the command list for newmod, we can deduce that the option we want is -n, which is the default option, so we can just type MODULE_NAME directly after newmod. In fact, it is recommended that you avoid using "-n" as it overrides the auto-detected name for other commands. For now, we ignore the other options.

2. Create a new block

$ gr_modtool newmod tutorial
Creating out-of-tree module in ./gr-tutorial... Done.
Use 'gr_modtool add' to add a new block to this currently empty module.

We should now see a new folder, gr-tutorial, in the current directory. Let's examine this folder to see what gr_modtool does for us.

gr-tutorial$ ls
apps  cmake  CMakeLists.txt  docs  examples  grc  include  lib  Python  swig

Since we are using Python in this tutorial, we only need to care about the Python folder and the grc folder. Before we can look at the code, we need to create a new block from the template. There are four different types of Python blocks here. However, it is too early to discuss this. We'll use a synchronous 1:1 input-output block to keep the interpretation simple (this block has the same number of outputs as the inputs, but don't worry about that for now).

Now that we know the language we want to write the block in (Python), and the type of block (synchronous), we can now add the block to our module. Again, we need to run the gr_modtool help command until we are familiar with the different commands. We found that the add command was what we wanted. Now we run help on the add command to see what we need to enter.

gr-tutorial$ gr_modtool help add
... (General Options from Last Help)
Add module options:
    -t BLOCK_TYPE, --block-type=BLOCK_TYPE
                        One of sink, source, sync, decimator, interpolator,
                        general, tagged_stream, hier, noblock.
    --license-file=LICENSE_FILE
                        File containing the license header for every source
                        code file.
    --argument-list=ARGUMENT_LIST
                        The argument list for the constructor and make
                        functions.
    --add-Python-qa     If given, Python QA code is automatically added if
                        possible.
    --add-cpp-qa        If given, C++ QA code is automatically added if
                        possible.
    --skip-cmakefiles   If given, only source files are written, but
                        CMakeLists.txt files are left unchanged.
    -l LANG, --lang=LANG
                        Language (cpp or Python)

We can see that -l LANG and -t BLOCK_TYPE are relevant for our example. So when we create a new block, we learn about the command.
Enter "multiply_py_ff" when prompted for name, "multiple" when prompted for argument list, "y" when prompted for Python QA (Quality Assurance), or just hit enter (capitals are the default).

gr-tutorial$ gr_modtool add -t sync -l python
GNU Radio module name identified: tutorial
Language: Python
Enter name of block/code (without module name prefix): multiply_py_ff
Block/code identifier: multiply_py_ff
Enter valid argument list, including default arguments: multiple
Add Python QA code? [Y/n] y
Adding file 'Python/multiply_py_ff.py'...
Adding file 'Python/qa_multiply_py_ff.py'...
Editing Python/CMakeLists.txt...
Adding file 'grc/tutorial_multiply_py_ff.xml'...
Editing grc/CMakeLists.txt...

We noticed 5 changes: two changes in the CMakeLists.txt file, a new file qa_multiply_py_ff.py to test our code, a new file multiply_py_ff.py for the function section, and a new file tutorial_multiply_py_ff.xml to use to connect block and GRC. All this happens in the Python and grc subfolders.

3. Modify the Python block file

Let's start with the multiply_py_ff.py file in the Python folder. After opening it it looks like this:

import numpy
from gnuradio import gr

class multiply_py_ff(gr.sync_block):
    """
    docstring for block multiply_py_ff
    """
    def __init__(self, multiple):
        gr.sync_block.__init__(self,
            name="multiply_py_ff",
            in_sig=[<+numpy.float+>],
            out_sig=[<+numpy.float+>])
        self.multiple = multiple


    def work(self, input_items, output_items):
        in0 = input_items[0]
        out = output_items[0]
        # <+signal processing here+>
        out[:] = in0
        return len(output_items[0])

Let's look at this first Python instance line by line. We're already familiar with imports so let's skip those lines. We're familiar with Python's constructor (init) so we can immediately see that if we use our variable "multiple" first, we need to add another line. Let's not forget to keep these spaces because some code editors like to tab new lines. How can we use the variable multiple?
How to use the variable multiple...

def __init__(self, multiple):
        self.multiple = multiple
        gr.sync_block.__init__(self,

We noticed "<...>" in many places. These placeholders are from gr_modtool and tell us where we need to adjust.

in_sig=[<+numpy.float+>]
out_sig=[<+numpy.float+>]

gr.sync_block.init takes 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 "<" and ">". If we want vectors, we can define these as in_sig=[(numpy.float32,4),numpy.float32]. This means there are two input ports, one corresponding to a vector of 4 floats and one corresponding to a scalar. It's worth pointing out that if in_sig doesn't contain anything, it becomes a source block, and if out_sig doesn't contain anything, it becomes a sink block (if we change return len(output_items[0]) to returnlen( input_items[0]) because output_items is empty). We should modify it as follows:

in_sig=[numpy.float32]
out_sig=[numpy.float32]

Another part of the code with placeholders is in the work function, but first let's understand the work function better:

def work(self, input_items, output_items)

The work function is where the actual processing takes place, where the code we want to write is stored. Since this is a sync block, the number of input items is always equal to the number of output items, because a synchronized block ensures a fixed output-to-input rate. There are also decim blocks and interp blocks, where the number of output items is a multiple of the number of input items specified by the user. Now let's take a look at the placeholders:

in0 = input_items[0]
out = output_items[0]
# <+signal processing here+>
out[:] = in0
return len(output_items[0])

"in0" and "out" just store the input and output in a variable to make the block easier to write. The signal handler can be anything, including if statements, loops, function calls, etc., but for this example we just need to modify the out[:] = in0 line so that our input signal is multiplied by our variable. What do we need to add in order for in0 to multiply by our multiple?
How to multiply...

out[:] = in0*self.multiple

All right! Our block is now ready for multiplication, but to make sure it works correctly, we need to do a QA test!

4. QA testing

Now we need to test it to make sure it works correctly when we install it into GNU Radio. This is an important step, we must remember to include these tests in our code! Let's open qa_multiply_py_ff.py:

from gnuradio import gr, gr_unittest
from gnuradio import blocks
from multiply_py_ff import multiply_py_ff

class qa_multiply_py_ff (gr_unittest.TestCase):

    def setUp (self):
        self.tb = gr.top_block ()

    def tearDown (self):
        self.tb = None

    def test_001_t (self):
        # set up fg
        self.tb.run ()
        # check data


if __name__ == '__main__':
    gr_unittest.run(qa_multiply_py_ff, "qa_multiply_py_ff.xml")

gr_unittest adds support for checking approximate equality of tuples of floats and complex numbers. All we need to worry about is the def test_001_t function. We know we need to enter data so let's create the data. We want it to be in vector form so that we can test many values ​​at once. Let's create a vector of floats.

src_data = (0, 1, -2, 5.5, -0.5)

We also need the output data so that we can compare it with the input data to see if it is working correctly as we expected. Let's simply multiply by 2.

expected_result = (0, 2, -4, 11, -1)

Now we've created a flow graph like we did when we first introduced Python with GNU Radio. We can use block library, especially vector_source_f function and vector_sink_f function, in the manual we can read. Let's assign three variables "src", "mult" and "snk" to block. as follows:

src = blocks.vector_source_f(src_data)
mult = multiply_py_ff(2)
snk = blocks.vector_sink_f()

Now we need to press src>mult>snk to connect everything. Instead of using self.connect in other blocks, we need to use self.tb.connect because of the setUp function. Below is how we connect src block and mult block.

self.tb.connect (src, mult)

How do we connect other blocks?

self.tb.connect (mult, snk)

Then we can run the flow graph and store data from the sink like this:

self.tb.run ()
result_data = snk.data ()

Finally, we can run our comparison function and have it tell us if the numbers in the 6 positions match. We use assertFloatTuplesAlmostEqual instead of the "regular assert functions" included in python's unit test https://docs.python.org/2/library/unittest.html#assert-methods because there may be cases where floats cannot be rounded up Get the case where a=b.

self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)

Overall the new test_001_t function should look like this:

src_data = (0, 1, -2, 5.5, -0.5)
expected_result = (0, 2, -4, 11, -1)
src = blocks.vector_source_f (src_data)
mult = multiply_py_ff (2)
snk = blocks.vector_sink_f ()
self.tb.connect (src, mult)
self.tb.connect (mult, snk)
self.tb.run ()
result_data = snk.data ()
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)

We can then go to the python directory and run:

gr-tutorial/python$ python qa_multiply_py_ff.py
.
----------------------------------------------------------------------
Ran 1 test in 0.004s

OK

At this point, we should also change the number in src_data to make sure the block is actually checking the value and seeing what the error looks like. Python allows to test blocks quickly without compiling; simply change something and rerun the QA tests.

5. XML file

So far, we have written a Python block and a QA test against this block. What we have to do next is to edit the XML file in grc so that we can further use it in GRC. GRC uses XML files to set all the options we see. We don't have to write any Python or C++ code to get a block to display in GRC, but we do need to connect it. We go into the grc folder where all the XML files are located. There is a tool in gr_modtool called makexml, but it only works with C++ blocks. Let's open the tutorial_multiply_py_ff.xml file:

<?xml version="1.0"?>
<block>
  <name>multiply_py_ff</name>
  <key>tutorial_multiply_py_ff</key>
  <category>tutorial</category>
  <import>import tutorial</import>
  <make>tutorial.multiply_py_ff($multiple)</make>
  <!-- Make one 'param' node for every Parameter you want settable from the GUI.
       Sub-nodes:
       * name
       * key (makes the value accessible as $keyname, e.g. in the make node)
       * type -->
  <param>
    <name>...</name>
    <key>...</key>
    <type>...</type>
  </param>

  <!-- Make one 'sink' node per input. Sub-nodes:
       * name (an identifier for the GUI)
       * type
       * vlen
       * optional (set to 1 for optional inputs) -->
  <sink>
    <name>in</name>
    <type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
  </sink>

  <!-- Make one 'source' node per output. Sub-nodes:
       * name (an identifier for the GUI)
       * type
       * vlen
       * optional (set to 1 for optional inputs) -->
  <source>
    <name>out</name>
    <type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
  </source>
</block>

We can change the name shown, and the category it will show in GRC. category is where this block will be found in GRC. We can browse the file and find the modtool's identifier. The first one looks like this:

<!-- Make one 'param' node for every Parameter you want settable from the GUI.
       Sub-nodes:
       * name
       * key (makes the value accessible as $keyname, e.g. in the make node)
       * type -->

This refers to the parameter we used when we first created the block: the variable "multiple". We can fill in as follows:

  <param>
    <name>Multiple</name>
    <key>multiple</key>
    <type>float</type>
  </param>

The next placeholder can be found in the sink and source tags:

 <sink>
    <name>in</name>
    <type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
  </sink>

We can see that if we need the input type we can simply erase everything in the label and replace it with "float". This applies to this block. The best way to gain more experience writing xml files is to look at the source code of these pre-existing blocks, such as the existing Multiply block.

6. Install Python block

Now that we have edited the XML file, we are ready to install the block for GRC. First, we need to leave the /grc directory and create a new directory called "build". Inside the build directory, we can run a series of commands:

cmake ../
make
sudo make install
sudo ldconfig

Then we can open GRC to view the new block.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326493377&siteId=291194637