1.使用gr_modtool
在我们开始之前,我们需要了解gr_modtool的命令都是什么,所以我们来看看帮助。
$ 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
我们会看到这里有很多可用的命令。在本教程中我们只关心newmod和add;然而,其它详尽的解释可以让你在没有指导的情况下灵活地使用其它的gr_modtool命令。
首先,我们注意到我们还能请求单个命令的更多的信息。让我们从newmod开始,因为这是用来创建新的树外模块的命令。
$ 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
现在我们可以读到newmod的命令列表,我们能推断出我们想要的选项是-n,这个是默认选项,所以我们可以在newmod后直接输入MODULE_NAME就行了。实际上,建议您避免使用“-n”,因为对于其他命令,它会覆盖自动检测到的名称。现在,我们忽略其他的选项。
2.建立一个新的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.
我们现在应该会在当前目录看到一个新的文件夹,gr-tutorial。让我们检查这个文件夹来看看gr_modtool为我们做了什么。
gr-tutorial$ ls
apps cmake CMakeLists.txt docs examples grc include lib Python swig
由于我们在这个教程中用的是Python,我们只需要关心Python文件夹和grc文件夹。在我们查看代码前,我们需要从模板创建一个新的block。这里有四个不同类型的Python block。然而现在讨论这个太早。我们会使用synchronous 1:1输入输出block来让解释变得简单(这个block的输出和输入的数量一样,但现在不用担心这个)。
现在我们知道我们想要写入block的语言(Python),和block的类型(synchronous),我们现在可以向我们的模块添加block了。再一次,我们需要运行gr_modtool help命令直到我们熟悉了不同的命令。我们发现add命令是我们想要的。现在我们在add命令上运行help来看看我们需要输入什么。
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)
我们可以看到-l LANG和-t BLOCK_TYPE与我们的例子相关。因此在我们创建新的block时,我们了解了命令。
当提醒输入name时输入“multiply_py_ff”,提醒argument list时输入“multiple”,提醒Python QA(质量保证)时输入“y”,或只敲击回车键(大写字母是默认值)。
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...
我们注意到5个改变:两个CMakeLists.txt文件中的改变,一个新的文件qa_multiply_py_ff.py用来测试我们的代码,一个新的文件multiply_py_ff.py是函数部分,一个新的文件tutorial_multiply_py_ff.xml用来连接block和GRC。所有这些发生在Python和grc子文件夹中。
3.修改Python block文件
让我们从Python文件夹下的multiply_py_ff.py文件开始。打开它后如下所示:
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])
让我们一行一行地看这第一个Python实例。我们已经对imports熟悉了所以我们跳过这些行。我们对Python的构造体(init)熟悉了所以可以立即看到如果我们先用我们的变量“multiple”,我们需要添加另一行。我们不要忘记保留这些空位因为一些代码编辑器喜欢对新的行添加tab。我们如何使用变量multiple?
如何使用变量multiple……
def __init__(self, multiple):
self.multiple = multiple
gr.sync_block.__init__(self,
我们注意到在许多地方有”<…>”。这些占位符来自于gr_modtool,告诉我们哪里需要我们调整。
in_sig=[<+numpy.float+>]
out_sig=[<+numpy.float+>]
gr.sync_block.init需要4个输入:self,name和输入输出向量的size/type。首先,我们想通过移除“<”和“>”使项目大小为一个单精度float或numpy.float32。如果我们想要向量,我们可以定义这些为in_sig=[(numpy.float32,4),numpy.float32]。这意味着有两个输入端口,一个对应于4个floats的向量,另外一个对应于标量。值得指出的是如果in_sig不包含任何东西,它会变为一个source block,如果out_sig不包含任何东西,那么它会变为一个sink block(假如我们把return len(output_items[0]) 变为 returnlen(input_items[0]),因为output_items是空的)。我们应该按如下修改:
in_sig=[numpy.float32]
out_sig=[numpy.float32]
另外一部分有占位符的代码在work函数里,但首先让我们更好地了解work函数:
def work(self, input_items, output_items)
work函数是实际的处理过程发生的地方,这里存放我们想写的代码。由于这是一个sync block,输入项目的数量总是等于输出项目的数量,因为同步的block确保固定的输出到输入的速率。同样还有decim block抽取块和interp block内插块,其中输出项的数量是用户指定的输入项的数量的倍数。现在我们看一看占位符:
in0 = input_items[0]
out = output_items[0]
# <+signal processing here+>
out[:] = in0
return len(output_items[0])
“in0”和”out”只是把输入和输出存进一个变量来让block写起来更容易。信号处理过程可以是任何东西,包括if语句、循环、函数调用等,但是对于这个例子我们只需要修改out[:] = in0行,以便让我们的输入的信号乘以我们的变量倍数。为了让in0乘以我们的倍数我们需要添加什么?
如何相乘……
out[:] = in0*self.multiple
好了!我们的block现在可以进行相乘了,但是为了确保正确运行,我们需要进行质量保证QA测试!
4. QA测试
现在我们需要测试它以便确认当我们把它安装进GNU Radio时它能正确地运行。这是很重要的一步,我们必须记住把这些测试包含进我们的代码!让我们打开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添加了对检查浮点和复数的元组的近似相等的支持。我们只需要担心的是def test_001_t函数。我们知道我们需要输入数据所以让我们创建数据。我们想让它是向量形式以便我们能一次测试许多值。让我们创建一个浮点数向量。
src_data = (0, 1, -2, 5.5, -0.5)
我们同样需要输出数据以便我们与输入数据比较来确定它是否正确地按我们预期的那样工作。让我们简单地乘以2。
expected_result = (0, 2, -4, 11, -1)
现在我们像我们第一次介绍在GNU Radio中使用Python时的样子创建了一个流图。我们可以使用block库,特别是vector_source_f函数和vector_sink_f函数,在手册中我们可以读到。让我们给block分配三个变量”src”,”mult”和”snk”。如下:
src = blocks.vector_source_f(src_data)
mult = multiply_py_ff(2)
snk = blocks.vector_sink_f()
现在我们需要按src>mult>snk来连接起所有的东西。与我们在其它的block中使用self.connect不一样,我们需要用self.tb.connect因为setUp函数。下面是我们如何连接src block和mult block。
self.tb.connect (src, mult)
我们如何连接其它的block?
self.tb.connect (mult, snk)
然后我们可以运行流图并从sink存储数据,如下所示:
self.tb.run ()
result_data = snk.data ()
最后,我们可以运行我们的比较函数并让它告诉我们6个位置的数字是否相符。我们使用assertFloatTuplesAlmostEqual而不是包含在python的单元实验中的”regular assert functions”https://docs.python.org/2/library/unittest.html#assert-methods因为可能会有由于浮点数凑整而不能得到a=b的情况。
self.assertFloatTuplesAlmostEqual (expected_result, result_data, 6)
总体上新的test_001_t函数应该按如下所示:
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)
我们接下来可以去python目录运行:
gr-tutorial/python$ python qa_multiply_py_ff.py
.
----------------------------------------------------------------------
Ran 1 test in 0.004s
OK
这时,我们也应该改变一下src_data中的数字来确保block确实在检查数值并看一看错误是什么样的。Python允许很快地测试block而不用编译;简单地改变一些东西并重新运行QA测试。
5. XML文件
至此,我们已经写了Python block和针对这个block的QA测试。下面我们要做的是编辑grc中的XML文件以便我们能进一步在GRC中使用它。GRC使用XML文件来设置我们看到的所有选项。我们不用写任何Python或C++代码来让一个block在GRC中显示,但我们确实需要连接它。我们进入grc文件夹,所有的XML文件位于其中。gr_modtool中有一个工具叫做makexml,但它只适用于C++ block。让我们打开tutorial_multiply_py_ff.xml文件:
<?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>
我们可以更改显示出的name,和它会在GRC中显示的category。category是这个block在GRC中会被找到的位置。我们可以浏览文件并找到modtool的标识符。第一个如下所示:
<!-- 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 -->
这指的是我们最开始创建block时用到的参数:变量“multiple”。我们可以按如下填写:
<param>
<name>Multiple</name>
<key>multiple</key>
<type>float</type>
</param>
下一个占位符可以再sink和source标签处找到:
<sink>
<name>in</name>
<type><!-- e.g. int, float, complex, byte, short, xxx_vector, ...--></type>
</sink>
我们可以看到如果需要输入类型我们可以简单地擦除标签里所有的东西换为“float”。这适用于这个block。获得更多的写xml文件的经验的最好的方法是查看这些以前就有的block的源代码,如已经有的相乘block。
6.安装Python block
既然我们已经编辑了XML文件,那么我们已经准备好了给GRC安装block了。首先,我们需要从/grc目录离开然后创建一个新的目录名为“build”。在build目录里,我们可以运行一系列命令:
cmake ../
make
sudo make install
sudo ldconfig
然后我们就可以打开GRC查看新的block了。