# Copyright (C) 2017 Free Software Foundation, Inc. # # Permission is granted to copy, distribute and/or modify this document # under the terms of the GNU Free Documentation License, Version 1.3 # or any later version published by the Free Software Foundation; # with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. # A copy of the license is included in the section entitled "GNU # Free Documentation License". /*! \page page_python_blocks Python Blocks How to create blocks in Python \section pyblocks_streaming Streaming Data Blocks We create blocks in Python very much like we would in C++, just with more Python. Figure out which type of block you want to create: \li general block (gr.basic_block) \li synchronous block (gr.sync_block) \li decimator (gr.sync_decimator) \li interpolator (gr.sync_interpolator) The block class inherits from one of these base classes, and then in defining the parent class, we set the I/O signature. However, unlike in C++ where we use the gr::io_signature class, here we can just create a Python list of the I/O data sizes using numpy data types: \li numpy.int8 \li numpy.int16 \li numpy.int32 \li numpy.float32 \li numpy.float64 Like a normal C++ version of the block, we then create and initialize any variables in the constructor, define any setters and getters, and create the work function. The prototype for the work function is quite simple: \code def work(self, input_items, output_items) \endcode The input_items and output_items are lists of lists. The input_items contains a vector of input samples for every input stream, and the output_items is a vector for each output stream where we can place items. Then length of output_items[0] is equivalent to the noutput_items concept we are so familiar with from the C++ blocks. Following is an example Python block that adds two input streams together. This block is used in the qa_block_gateway.py test code. \code class add_2_f32_1_f32(gr.sync_block): def __init__(self): gr.sync_block.__init__( self, name = "add 2 f32", in_sig = [numpy.float32, numpy.float32], out_sig = [numpy.float32], ) def work(self, input_items, output_items): output_items[0][:] = input_items[0] + input_items[1] return len(output_items[0]) \endcode The block defines two input floating point streams by setting in_sig to "[numpy.float32, numpy.float32]" and a single output float stream in out_sig of "[numpy.float32]." The work function then just adds the two input streams together. The streams are input_items[0] and input_items[1]. The block still returns the concept of noutput_items like we use in C++, only we get it here by getting len(output_items[0]). Because this is a sync_block, we also know that the size of the input_items for both streams is the same as the size of the output_items vector. \section pyblocks_tags Using Stream Tags Python blocks have access to the stream tag system like their C++ counterparts. The interface is almost identical except they behave just a bit more like we would expect in Python. To add tags to the data stream, we use the add_item_tag function: \code def work(self, input_items, output_items): .... add_item_tag(which_output, abs_offset, key, value, srcid) .... \endcode The abs_offset is an integer of the sample that the tag is attached to, and key and value are both PMTs to set the key:value pair of the tag information, and the srcid is an optional PMT to define the source of the block that generate the tag. We then can get tags using either the get_tags_in_range or get_tags_in_window. Again, like their C++ counter parts, the get_tags_in_range uses the absolute item offset numbering (using nitems_read) while the get_tags_in_window uses relative offsets within the current window of items available to the work function. 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: \code def work(self, input_items, output_items): .... tags = get_tags_in_window(which_input, rel_start, rel_end) .... \endcode \section pyblocks_msgs Using Message Passing Again, like their C++ counterparts, Python blocks can use the asynchronous message passing interface. We define output message handlers using: \code self.message_port_register_out(pmt.intern("<port name>")) \endcode We can then post messages to this using the message_port_pub function: \code self.message_port_pub(pmt.intern("<port name>"), <pmt message>) \endcode We then register input messages and handlers in similar ways: \code self.message_port_register_in(pmt.intern("<port name>")) self.set_msg_handler(pmt.intern("<port name>"), <msg handler function>) \endcode Putting this together below is a very simple example: \code class msg_block(gr.basic_block): def __init__(self): gr.basic_block.__init__( self, name="msg_block", in_sig=None, out_sig=None) self.message_port_register_out(pmt.intern('msg_out')) self.message_port_register_in(pmt.intern('msg_in')) self.set_msg_handler(pmt.intern('msg_in'), self.handle_msg) def handle_msg(self, msg): self.message_port_pub(pmt.intern('msg_out'), pmt.intern('message received!')) \endcode */