Debido a la popularidad de Python para el procesamiento digital de señales en el ámbito científico a través de SciPy, el proyecto de GNU Radio permite el desarrollo de bloques de procesamiento de señales que pueden ser escritos en lenguaje de programación Python y usar estas librerías sin ningún problema. Este tipo de módulos son conocidos como out-of-tree, ya que, aunque los módulos serán integrados dentro del catálogo de bloques de GNU Radio no se van a integrar al proyecto para su distribución conjunta, sino individual.

Procedimiento.

Para crear un módulo out-of-tree se ejecuta desde una terminal de linux el siguiente comando

$ gr_modtool create
Name of the new module:Tu_modulo
Creating out-of-tree module in ./gr-Tu_modulo... Done.
Use 'gr_modtool add' to add a new block to this currently empty module.

El nombre que se indique para el módulo es con el que va a aparecer en el catálogo de GNU Radio, en este caso Tu_modulo.

Para crear los bloques que integrarán el módulo se utiliza la herramienta gr_modtool en la carpeta raíz del proyecto. En este caso se creará un bloque que realizará la función de un convertidor binario a decimal para los tipos de datos float basado en Python.

$ cd gr-Tu_modulo
$ gr_modtool add
GNU Radio module name identified: Tu_modulo
('sink', 'source', 'sync', 'decimator', 'interpolator', 'general', 'tagged_stream', 'hier', 'noblock')
Enter block type: decimator
Language (python/cpp): python
Language: Python
Enter name of block/code (without module name prefix): bin2dec_py_ff
Block/code identifier: bin2dec_py_ff
Enter valid argument list, including default arguments: vec_size
Add Python QA code? [Y/n]
Adding file 'python/bin2dec_py_ff.py'...
Adding file 'python/qa_bin2dec_py_ff.py'...
Editing python/CMakeLists.txt...
Adding file 'grc/prueba_bin2dec_py_ff.xml'...
Editing grc/CMakeLists.txt...

y a su vez, se desarrollará un bloque que realice la operación inversa, esto es, un convertidor decimal a binario.

$ gr_modtool add
GNU Radio module name identified: Tu_modulo
('sink', 'source', 'sync', 'decimator', 'interpolator', 'general', 'tagged_stream', 'hier', 'noblock')
Enter block type: interpolator
Language (python/cpp): python
Language: Python
Enter name of block/code (without module name prefix): dec2bin_py_ff
Block/code identifier: dec2bin_py_ff
Enter valid argument list, including default arguments: vec_size
Add Python QA code? [Y/n]
Adding file 'python/dec2bin_py_ff.py'...
Adding file 'python/qa_dec2bin_py_ff.py'...
Editing python/CMakeLists.txt...
Adding file 'grc/prueba_dec2bin_py_ff.xml'...
Editing grc/CMakeLists.txt...

de esta manera se han creado dos bloques que serán escritos en Python, un decimador para el convertidor binario a decimal y un interpolador para el decimal a binario.

Los archivos donde se realizará la programación en código Python se ubican en ~/gr-Tu_modulo/python y se llama dec2bin_py_ff.py para el convertidor decimal a binario y bin2dec_py_ff.py para el convertidor binario a decimal.

Convertidor binario a decimal

Dentro del archivo bin2dec_py_ff.py se puede observar que la librería de Scipy con Numpy ha sido declarada, en caso de que se requiera alguna librería en particular sólo se agrega con la instrucción include.

En la parte de class bin2dec_py_ff(gr.decim_block) está la seccción donde se definen los parámetros iniciales del bloque, def __init__(self, vec_size):, en ella se define el tipo de datos de entrada y salida del bloque, así como el factor de decimación del bloque decim=vec_size. Para poder utilizar la variable vec_size fuera de la clase se puede utilizar self.vec_size, es importante notar que esta variable pertenece a la clase pero no a la función __init__, la variable vec_size representa la cantidad de bits que se requieren para convertir el número a decimal.

class bin2dec_py_ff(gr.decim_block):
    """
    docstring for block bin2dec_py_ff
    """
    def __init__(self, vec_size):
        gr.decim_block.__init__(self,
            name="bin2dec_py_ff",
            in_sig=[numpy.float32],
            out_sig=[numpy.float32], decim=vec_size)
    self.vec_size=vec_size

En la parte correspondiente a // Do <+signal processing+> es donde se realiza el procesamiento de datos que manipulará el bloque, para la conversión de binario a decimal se desarrolla el código de programación en Python, quedando de la siguiente manera:

def work(self, input_items, output_items):
	in0 = numpy.int_(input_items[0])
	out = output_items[0]
	j=0
	for i in range(self.vec_size,len(in0)+1,self.vec_size):
		out[j]= int(str("".join(str(x) for x in in0[i-self.vec_size:i])),2)
		j += 1
	return len(output_items[0])

El archivo quedaría de la siguiente forma, ya con todas las modificaciones incluidas:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2015 IVAN RODRIGUEZ.
#
# This is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

import numpy
from gnuradio import gr

class bin2dec_py_ff(gr.decim_block):
    """
    docstring for block bin2dec_py_ff
    """
    def __init__(self, vec_size):
        gr.decim_block.__init__(self,
            name="bin2dec_py_ff",
            in_sig=[numpy.float32],
            out_sig=[numpy.float32], decim=vec_size)
	self.vec_size=vec_size



    def work(self, input_items, output_items):
        in0 = numpy.int_(input_items[0])
        out = output_items[0]
        j=0
        for i in range(self.vec_size,len(in0)+1,self.vec_size):
			out[j]= int(str("".join(str(x) for x in in0[i-self.vec_size:i])),2)
			j += 1
        return len(output_items[0])

Convertidor decimal a binario

Para corroborar el correcto funcionamiento del bloque de convertidor binario a decimal se propone realizar un convertidor decimal a binario que realizará la operación inversa del bloque anterior.

El archivo donde se realiza la programación es dec2bin_py_ff.py. Nuevamente, en la sección de class dec2bin_py_ff(gr.interp_block): se indica los tipos de datos de entrada y salida que manejará el bloque de procesamiento, así también el factor de interpolación definido por la variable vec_size y por último la variable self.vec_size, quedando como sigue:

class dec2bin_py_ff(gr.interp_block):
    """
    docstring for block dec2bin_py_ff
    """
    def __init__(self, vec_size):
        gr.interp_block.__init__(self,
            name="dec2bin_py_ff",
            in_sig=[numpy.float32],
            out_sig=[numpy.float32], interp=vec_size)
    self.vec_size=vec_size

En la sección de // Do <+signal processing+> se escribe el código que realizará la conversión decimal a binario.

def work(self, input_items, output_items):
    in0 = input_items[0]
    out = output_items[0]
    f=numpy.zeros([len(in0),self.vec_size])
    for i in range(0,len(in0)):
        f[i,:] = numpy.int_([str(x) for x in numpy.binary_repr(in0[i], width = self.vec_size)])*1.0
    out[:] = numpy.hstack(f)
    return len(output_items[0])

El archivo completo queda de la siguiente manera:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright 2015 IVAN RODRIGUEZ.
#
# This is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3, or (at your option)
# any later version.
#
# This software is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this software; see the file COPYING.  If not, write to
# the Free Software Foundation, Inc., 51 Franklin Street,
# Boston, MA 02110-1301, USA.
#

import numpy
from gnuradio import gr

class dec2bin_py_ff(gr.interp_block):
    """
    docstring for block dec2bin_py_ff
    """
    def __init__(self, vec_size):
        gr.interp_block.__init__(self,
            name="dec2bin_py_ff",
            in_sig=[numpy.float32],
            out_sig=[numpy.float32], interp=vec_size)
	self.vec_size=vec_size


    def work(self, input_items, output_items):
		in0 = input_items[0]
		out = output_items[0]
		f=numpy.zeros([len(in0),self.vec_size])
		for i in range(0,len(in0)):
			f[i,:] = numpy.int_([str(x) for x in numpy.binary_repr(in0[i], width = self.vec_size)])*1.0
		out[:] = numpy.hstack(f)
		return len(output_items[0])

Interfaz GNU Radio Companion

Los bloques dentro de GNU Radio Companion (GRC) son archivos xml que se ubican en ~/gr-Tu_modulo/grc, para el convertidor binario a decimal se llama Tu_modulo_bin2dec_py_ff.xml y para el convertidor decimal a binario Tu_modulo_dec2bin_py_ff.xml.

Dentro del archivo xml hay varios parámetros que pueden ser personalizados, tal como el nombre del bloque, este parámetro se define en la línea correspondiente a <name>.

<name>Binary To Decimal Python</name>

En la parte de <param> se indica la variable de entrada del bloque

  <param>
    <name>Vector size</name>
    <key>vec_size</key>
    <type>int</type>
  </param>

El conector de entrada del bloque de procesamiento se define en <sink>

  <sink>
    <name>in</name>
    <type>float</type>
  </sink>

El conector de salida en source

  <source>
    <name>out</name>
    <type>float</type>
  </source>

De igual manera se modifican las secciones de <name>, <param>, <sink> y <source> en el archivo Tu_modulo_dec2bin_py_ff.xml para el bloque de procesamiento del convertidor decimal a binario.

El archivo quedaría finalmente de la siguiente forma:

<?xml version="1.0"?>
<block>
  <name>binary to decimal Python</name>
  <key>Tu_modulo_bin2dec_py_ff</key>
  <category>Tu_modulo</category>
  <import>import Tu_modulo</import>
  <make>Tu_modulo.bin2dec_py_ff($vec_size)</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>Vector size</name>
    <key>vec_size</key>
    <type>int</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>float</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>float</type>
  </source>
</block>

El archivo XML del convertidor decimal a binario quedaría de la siguiente forma:

<?xml version="1.0"?>
<block>
  <name>Decimal to binary Python</name>
  <key>Tu_modulo_dec2bin_py_ff</key>
  <category>Tu_modulo</category>
  <import>import Tu_modulo</import>
  <make>Tu_modulo.dec2bin_py_ff($vec_size)</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>Vector size</name>
    <key>vec_size</key>
    <type>int</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>float</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>float</type>
  </source>
</block>

Compilación de los bloques

Una vez ya realizado todos los procesos referentes al código de programación de los bloques de procesamiento se procede a integrarlos en el proyecto de GNU Radio. Esto se realiza por medio de una serie de comandos desde una terminal de linux en la raíz del proyecto. En caso de no tener una carpeta de build, se crea por medio de comandos de linux

$ mkdir build
$ cd build

Una vez dentro de la carpeta de build, se procede a compilar el proyecto

$ cmake ../
$ make
$ sudo make install
$ sudo ldconfig

De esa manera queda integrado el módulo out-of-tree dentro de GNU Radio.

Archivos de prueba QA

Un modo para corroborar el correcto funcionamiento del bloque de procesamiento es a través de los archivos QA que se ubican en ~/gr-Tu_modulo/python. Estos archivos permiten hacer una comparación de los datos de salida y los datos esperados de salida de acuerdo a una secuencia de datos de entrada previamente definida. Un ejemplo para probar el convertidor binario a decimal sería:

def test_001_t (self):
    # set up fg
    src_data = [1.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0]
    expected_result = [5.0, 7.0, 0.0, 1.0, 2.0]
    src = blocks.vector_source_f (src_data)
    conv = bin2dec_py_ff (3)
    snk = blocks.vector_sink_f ()
    self.tb.connect (src, conv)
    self.tb.connect (conv, snk)
    self.tb.run ()
    # check data
    result_data = snk.data ()
    self.assertFloatTuplesAlmostEqual (expected_result, result_data,2)

Una vez modificado el archivo QA, se procede a ejecutarlo desde una terminal de Linux con el comando de python, cabe recordar que la terminal debe abrirse en ~/gr-Tu_modulo/python.

$ python qa_dec2bin_py_ff.py
.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK

Si el código funciona correctamente, se nos mostrará que no hubo error al momento de ejecutar.

@viktor_ivan