---
The following are examples of different ways to work with custom GNU Radio blocks. The main functionality for blocks are contained in either Python or C++ with the graphical/input parameters configured in a .xml or .yml file depending on the GNU Radio version.
## Table of Contents
1. [References](#references)
2. [Creating from Scratch](#from_scratch)
3. [Creating Input Parameters](#input_parameters)
4. [Adding Stream Tags](#adding_stream_tags)
5. [Reading Stream Tags](#reading_stream_tags)
6. [Removing Tags](#removing_tags)
7. [Use the Last N Samples](#last_n_samples)
8. [Message Passing](#message_passing)
9. [PDUs](#pdus)
10. [Reusing Existing GNU Radio Types](#existing_gnu_radio_types)
## References
- https://wiki.gnuradio.org/index.php/Guided_Tutorial_GNU_Radio_in_Python#3.2.1._Using_gr_modtool
- https://wiki.gnuradio.org/index.php/OutOfTreeModules
- https://github.com/dzlabsio/gr-callback_xml
- https://wiki.gnuradio.org/index.php/Message_Passing
## Creating from Scratch
**Generic Python Block**
https://wiki.gnuradio.org/index.php/Guided_Tutorial_GNU_Radio_in_Python#3.2.1._Using_gr_modtool
https://wiki.gnuradio.org/index.php/OutOfTreeModules
```
gr_modtool newmod
cd
gr_modtool add -t sync -l python
```
```
gr_modtool: option -t: (choose from 'sink', 'source', 'sync', 'decimator',
'interpolator', 'general', 'tagged_stream', 'hier', 'noblock')
```
**Edit the Main Python File**
```xml
def __init__(self):
gr.sync_block.__init__(self,
name="downlink_tagger",
in_sig=[<+numpy.float+>],
out_sig=[<+numpy.float+>])
```
Types: numpy.complex64, numpy.float32, numpy.uint8
**Edit the *./grc/* XML File**
```python
downlink_taggerlightbridge_downlink_taggerlightbridgeimport lightbridgelightbridge.downlink_tagger().........inout
```
**Make/Add Module to FISSURE**
Copy *gr-name* folder to *~/Fissure/Custom_Blocks/maint-3.x* and build the module. Afterwards, click the "Reload Blocks" arrow in GNU Radio Companion to load the new custom blocks.
```
cd *~/FISSURE/Custom_Blocks/maint-3.7/gr-*
mkdir build
cd build
cmake ..
make
sudo make install
sudo ldconfig
```
**Refresh GNU Radio Companion**
Click the blue arrow to reload the blocks.

**Making C++ Blocks**
```
gr_modtool add -t sync -l cpp
```
Similar procedure to Python but use this for the complex data type:
```
gr_complex
```
## Creating Input Parameters
In the xml:
```xml
lightbridge.downlink_tagger($high_threshold,$low_threshold)set_high_threshold($high_threshold)set_low_threshold($low_threshold)high_thresholdhigh_threshold"0.4"floatlow_thresholdlow_threshold"0.01"float
```
In the Python file:
```python
class downlink_tagger(gr.sync_block):
"""
docstring for block downlink_tagger
"""
def __init__(self, high_threshold, low_threshold):
gr.sync_block.__init__(self,
name="downlink_tagger",
in_sig=[numpy.complex64],
out_sig=[numpy.complex64])
self.high_threshold = high_threshold
self.low_threshold = low_threshold
def set_high_threshold(self,high_threshold):
self.high_threshold = float(high_threshold)
def set_low_threshold(self,low_threshold):
self.low_threshold = float(low_threshold)
```
For Callbacks in C++:
https://github.com/dzlabsio/gr-callback_xml
## Adding Stream Tags
```python
import numpy as np
from gnuradio import gr
import pmt
class blk(gr.sync_block):
def __init__(self):
gr.sync_block.__init__(
self,
name='Embedded Python Block',
in_sig=[np.complex64],
out_sig=[np.complex64]
)
def work(self, input_items, output_items):
for indx, sample in enumerate(input_items[0]):
if np.random.rand() > 0.95: # 5% chance this sample is chosen
key = pmt.intern("example_key")
value = pmt.intern("example_value")
self.add_item_tag(0, self.nitems_written(0) + indx, key, value)
# note: (self.nitems_written(0) + indx) is our current sample, in absolute time
output_items[0][:] = input_items[0] # copy input to output
return len(output_items[0])
```
## Reading Stream Tags
To get tags from a particular input stream, we have two functions we can use:
gr::block::get_tags_in_range: Gets all tags from a particular input port between a certain range of items (in absolute item time).
gr::block::get_tags_in_window: Gets all tags from a particular input port between a certain range of items (in relative item time within the work function).
The difference between these functions is working in absolute item time versus relative item time. Both of these pass back vectors of gr::tag_t, and they both allow specifying a particular key (as a PMT symbol) to filter against (or the fifth argument can be left out to search for all keys). Filtering for a certain key reduces the effort inside the work function for getting the right tag's data.
For example, this call just returns any tags between the given range of items:
```
#include
#include
void get_tags_in_range(std::vector &v,
unsigned int which_input,
uint64_t abs_start,
uint64_t abs_end);
std::vector tags;
get_tags_in_range(tags, 0, nitems_read(0), nitems_read(0) + framesize);
for (unsigned t = 0; t < tags.size(); t++) {
int offset = tags[t].offset - nitems_read(0);
tags[t].offset = offset + nitems_written(0);
add_item_tag(0, tags[t]);
printf("\n[*] TAG FOUND: %s", (pmt::symbol_to_string(tags[t].key)).c_str());
```
Adding a fifth argument to this function allows us to filter on the key key.
```
void get_tags_in_range(std::vector &v,
unsigned int which_input,
uint64_t abs_start,
uint64_t abs_end,
const pmt::pmt_t &key);
```
In Python, the main difference from the C++ function is that instead of having the first argument be a vector where the tags are stored, the Python version just returns a list of tags. We would use it like this:
```python
def work(self, input_items, output_items):
....
tags = get_tags_in_window(which_input, rel_start, rel_end)
tags = self.get_tags_in_window(0, 0, input_len,pmt.string_to_symbol("downlink"))
for tag in tags: #.offset, .key, .value
print str(tag.offset)
....
```
## Removing Tags
Remove all tags coming in and then you can make your own tags going out. The function for removing a single tag is deprecated.
```python
from gnuradio import gr
import pmt
class cp_remover(gr.sync_block):
def __init__(self):
gr.sync_block.__init__(self,
name="cp_remover",
in_sig=[numpy.complex64],
out_sig=[numpy.complex64])
# Don't Propagate Tags
self.set_tag_propagation_policy(gr.TPP_DONT)
def work(self, input_items, output_items):
in0 = input_items[0]
in0_len = len(in0)
# Make Tag
key = pmt.intern("test")
value = pmt.intern("test1")
self.add_item_tag(0, self.nitems_written(0)+0, key, value)
std::vector tags;
get_tags_in_range(tags, 0, nitems_read(0), nitems_read(0) + noutput_items, d_reset_tag_key);
for (unsigned t = 0; t < tags.size(); t++) {
int offset = tags[t].offset - nitems_read(0);
tags[t].offset = offset + nitems_written(0);
add_item_tag(0, tags[t]);
}
```
## Use the Last N Samples
How does the history work?
The history is the number of items a block needs to calculate 1 output item. For a filter, this is equal to the number of taps.
For a simple differentiator (y(n) = x(n) - x(n-1)), the history equals 2. Obviously, the smallest value for the history is 1.
When you are in the work function, the number of items in your input buffer equals the number of input items plus the history minus one.
Here is an example for an accumulator that outputs the sum of the last N items:
```python
for (int i = 0; i < noutput_items; i++) {
out[i] = 0;
for (int k = 0; k < history(); k++) {
out[i] += in[i+k];
}
}
```
As you can see, noutput_items items of out[] are written, whereas noutput_items + history() - 1 items of in[] are read from.
If the history has the value N, the first N-1 items are "old" items, i.e. they were available in the previous call to work() (when work() is called the first time, they are set to zero).
## Message Passing
Must register input, output ports when using "message" as a block output type:
```
self.message_port_register_in(pmt.intern("in_port_name"))
self.message_port_register_out(pmt.intern("out_port_name"))
self.set_msg_handler(pmt.intern("port name"), )
self.set_msg_handler(pmt.intern("port name"), self.handle_msg) # No () and no input parameters
def handle_msg(self, msg):
snr = pmt.to_float(msg)
```
```
const pmt::pmt_t d_port; // in .h private:
d_port(pmt::mp("header_data")) // in impl before {}
message_port_register_out(d_port); // in impl in the {}
message_port_pub(d_port, pmt::PMT_F);
pmt::pmt_t dict(pmt::make_dict());
for (unsigned i = 0; i < tags.size(); i++) {
dict = pmt::dict_add(dict, tags[i].key, tags[i].value);
}
message_port_pub(d_port, dict);
message_port_pub(d_snr_port, pmt::from_float(snr_avg));
message_port_register_in(pmt::mp("print"));
set_msg_handler(pmt::mp("print"),
boost::bind(&message_debug_impl::print, this, _1));
void message_debug_impl::print(pmt::pmt_t msg)
{
std::cout << "***** MESSAGE DEBUG PRINT ********\n";
pmt::print(msg);
std::cout << "**********************************\n";
}
```
https://wiki.gnuradio.org/index.php/Message_Passing
## PDUs
```
import pmt
msg = pmt.to_python(pmt.cdr(msg)) # [ 67 104 97 110 103 101 70 114 101 113 117 101 110 99 121 82 101 113 117 101 115 116]
msg = "".join([chr(item) for item in msg]) # convert to string
```
```
import zmq
import numpy, pmt
class ZMQ_pub(object):
def __init__(self, portOut):
self._ipAddress = '127.0.0.1'
self._portOut = portOut
self._zmqContext = zmq.Context()
self._socketOut = self._zmqContext.socket(zmq.PUB)
self._socketOut.bind('tcp://%s:%s' % (self._ipAddress, self._portOut))
def send(self, data):
car = pmt.make_dict()
data = bytes(data)
data = numpy.frombuffer(data, dtype=numpy.uint8)
cdr = pmt.to_pmt(data)
pdu = pmt.cons(car, cdr)
self._socketOut.send(pmt.serialize_str(pdu))
```
```
import zmq
from zmq import Again as ZMQ_sub_timeout
import numpy, pmt
import binascii
class ZMQ_sub(object):
def __init__(self, portIn, timeout = 100):
self._ipAddress = '127.0.0.1'
self._portIn = portIn
self._zmqContext = zmq.Context()
self._socketIn = self._zmqContext.socket(zmq.SUB)
self._socketIn.RCVTIMEO = timeout
self._socketIn.connect('tcp://%s:%s' % (self._ipAddress,self._portIn))
try:
self._socketIn.setsockopt(zmq.SUBSCRIBE, '') # python2
except TypeError:
self._socketIn.setsockopt_string(zmq.SUBSCRIBE, '') # python3, if this throws an exception... give up...
def recv(self):
msg = self._socketIn.recv()
pdu = pmt.deserialize_str(msg)
cdr = pmt.to_python(pmt.cdr(pdu))
cdr = numpy.getbuffer(cdr)
return cdr
```
## Reusing Existing GNU Radio Types
Edit CMakeLists.txt:
```
########################################################################
# Find gnuradio build dependencies
########################################################################
find_package(CppUnit)
find_package(Doxygen)
# Search for GNU Radio and its components and versions. Add any
# components required to the list of GR_REQUIRED_COMPONENTS (in all
# caps such as FILTER or FFT) and change the version to the minimum
# API compatible version required.
#set(GR_REQUIRED_COMPONENTS RUNTIME)
set(GR_REQUIRED_COMPONENTS RUNTIME DIGITAL ANALOG FILTER FFT BLOCKS PMT)
```
Edit swig.i:
```
/* -*- c++ -*- */
#define ADJULINK1_API
#define DIGITAL_API
#define FFT_API
%include "gnuradio.i" // the common stuff
//load generated python docstrings
%include "AdjuLink1_swig_doc.i"
%{
#include "AdjuLink1/additive_scrambler1.h"
#include "AdjuLink1/ofdm_carrier_allocator1.h"
#include "AdjuLink1/ofdm_cyclic_prefixer1.h"
#include "AdjuLink1/ofdm_channel_est1.h"
#include "AdjuLink1/ofdm_frame_equalizer1.h"
#include "AdjuLink1/protocol_formatter1.h"
#include "AdjuLink1/ofdm_serializer1.h"
#include "AdjuLink1/constellation_decoder1.h"
%}
//%include "gnuradio/digital/ofdm_equalizer_simpledfe.h"
%include "gnuradio/digital/ofdm_equalizer_base.h"
%include "gnuradio/digital/header_format_base.h"
%include "gnuradio/digital/constellation.h"
```
Use `gr::package::type` when referencing:
```
gr::digital::constellation_sptr constellation
```