Python Block Message Passing: Difference between revisions

From GNU Radio
Jump to navigation Jump to search
 
(52 intermediate revisions by 6 users not shown)
Line 1: Line 1:
{{TutorialNavigation}}
<div style="float:right">
{{Template:BeginnerTutorials}}
</div>
This tutorial describes how to read and write messages using the ''Embedded Python Block''.


The previous tutorial, [[Creating_Your_First_Block|Creating Your First Block]], demonstrates how to create a Python block using the ''Embedded Python Block''. The next tutorial, [[Low_Pass_Filter_Example|Low Pass Filter Example]], demonstrates how to use filtering blocks in GNU Radio.
The previous tutorial, [[Python_Block_with_Vectors|Python Block with Vectors]], demonstrates how to write an ''Embedded Python Block'' with vector inputs and outputs. The next tutorial, [[Python_Block_Tags|Python Block Tags]], describes how to read and write tags in a Python block.


== Message Overview ==
== Message Overview ==
Line 7: Line 10:
Messages are an asynchronous way to send information between blocks. Messages are good at conveying control data, maintaining a consistent state across blocks and providing some forms of non-data feedback to blocks in a flowgraph.
Messages are an asynchronous way to send information between blocks. Messages are good at conveying control data, maintaining a consistent state across blocks and providing some forms of non-data feedback to blocks in a flowgraph.


Messages have a couple unique properties:
Messages have a couple of unique properties:
* There is no sample-clock based guarantee when messages will arrive
* There is no sample-clock based guarantee when messages will arrive
* Messages are not associated with a specific sample like a tag
* Messages are not associated with a specific sample like a tag
Line 21: Line 24:
== Flowgraph Overview ==
== Flowgraph Overview ==


The following flowgraph will demonstrate how to:
The following flowgraph demonstrates how to:
* Add message sending and receiving ports to Python blocks
* Add message sending and receiving ports to Python blocks
* Transmit messages
* Send messages
* Receive and handle messages
* Receive and handle messages
* Adapt block behavior in the ''work()'' function based on a received messages
* Adapt block behavior in the ''work()'' function based on a received messages


Two custom ''Embedded Python Blocks'' will be created to:
Two custom ''Embedded Python Blocks'' are to be created to:
* Select, or [https://en.wikipedia.org/wiki/Multiplexer multiplex], one of two input signals based on a receive message
* Select, or [https://en.wikipedia.org/wiki/Multiplexer multiplex], one of two input signals based on a received message
* Count the number of samples and send a message to the multiplexing block to switch inputs
* Count the number of samples and send a message to the multiplexing block to switch inputs


Line 44: Line 47:
== Multiplexer: Defining The Block ==
== Multiplexer: Defining The Block ==


Double-click the first ''Embedded Python Block'' and open the source in the editor:
Double-click the ''Embedded Python Block'' and open the source in the editor:


[[File:EditMultiplexerProperties.png|700px]]
[[File:EditMultiplexerProperties.png|700px]]




The ''example_param'' is not needed, so remove the variable ''example_param'' from the ''__init()__''function:
The ''example_param'' is not needed, so remove the variable ''example_param'' from the signature of the ''__init__() ''function:
<pre>def __init__(self):  # only default arguments here</pre>
<pre>def __init__(self):  # only default arguments here</pre>


Line 64: Line 67:


<pre>output_items[0][:] = input_items[0]</pre>
<pre>output_items[0][:] = input_items[0]</pre>
When all of these changes are made, the code should look as follows:


[[File:AddSecondInputEmbeddedPythonBlock.png|800px]]
[[File:AddSecondInputEmbeddedPythonBlock.png|800px]]




Save the code (CTRL+S) and return back to GRC. The name of the block will have changed and the block will now have two inputs. Connect the ''Noise Source'' and ''Signal Source'' to the two inputs:
Recall that Python requires the [https://www.w3schools.com/python/gloss_python_indentation.asp proper indentation]. By default, the ''Embedded Python Block'' uses indentation of in multiples of 4 spaces. Mixing tabs and spaces raises a syntax error:
 
[[File:TabsSpacesError.png|500px]]
 
 
Save the code and return back to GRC. Note how the name of the block has changed and the block now has two inputs. Connect the ''Noise Source'' and ''Signal Source'' to the two inputs:


[[File:MultiplexerWithTwoInputs.png|800px]]
[[File:MultiplexerWithTwoInputs.png|800px]]
Line 74: Line 84:
== Multiplexer: Defining Message Input Port ==
== Multiplexer: Defining Message Input Port ==


Return to the code editor. A input message port needs to be added. Create a variable to name the port,
Return to the code editor. An input message port needs to be added. Create a variable to store the message port name:
<pre>self.selectPortName = 'selectPort'</pre>
<pre>self.selectPortName = 'selectPort'</pre>


and then add a line to create, or ''register'', the input port:
Add a line to create, or ''register'', the message input port:
<pre>self.message_port_register_in(pmt.intern(self.selectPortName))</pre>
<pre>self.message_port_register_in(pmt.intern(self.selectPortName))</pre>


and finally, add a line to connect the input port with a message handler.
Add a line to connect the input port with a message handler.
<pre>self.set_msg_handler(pmt.intern(self.selectPortName), self.handle_msg)</pre>
<pre>self.set_msg_handler(pmt.intern(self.selectPortName), self.handle_msg)</pre>


Line 86: Line 96:




Save the code (CTRL+S). Notice that syntax errors are listed in the properties of the ''Embedded Python Block'':
Save the code. Notice that errors are listed in the properties of the ''Embedded Python Block'':


[[File:CodeEditorErrorExample.png|500px]]
[[File:CodeEditorErrorExample.png|500px]]
Line 99: Line 109:
== Multiplexer: Creating Message Handler ==
== Multiplexer: Creating Message Handler ==


The message handler function now has to be defined. A message handler is the function which is called when a message is received. This message handler will switch between the two input ports based on the received message. The received message will either be a Boolean True or False. Define a new variable under ''__init()__'' which will be the input selector,
A message handler is the function which is called when a message is received.
 
The message handler function has to be defined. This message handler switches between the two input ports based on the received message. The received message is a Boolean that is True or False. Define a new variable under ''__init__()'' which is the input selector,


<pre>self.selector = True</pre>
<pre>self.selector = True</pre>


Now define the ''handle_msg()'' function:
Define the ''handle_msg()'' function:


<pre>def handle_msg(self, msg):
<pre>def handle_msg(self, msg):
Line 110: Line 122:
The function ''pmt.to_bool()'' takes the message PMT and then converts the data type into Python's Boolean data type. PMTs are used in message passing to abstract data types. For example, messages can be used to send and receive strings, floats, integers and even lists. More information about PMTs can be found on the [[Polymorphic_Types_(PMTs)|Polymorphic Types (PMTs) wiki page]].
The function ''pmt.to_bool()'' takes the message PMT and then converts the data type into Python's Boolean data type. PMTs are used in message passing to abstract data types. For example, messages can be used to send and receive strings, floats, integers and even lists. More information about PMTs can be found on the [[Polymorphic_Types_(PMTs)|Polymorphic Types (PMTs) wiki page]].


[[File:AddMessageHandler.png|700px]]
[[File:AddSelectorAttribute.png|700px]]


== Multiplexer: Using a Message in ''work()'' ==
== Multiplexer: Using a Message in ''work()'' ==


The external interface of the multiplexer is complete. Now the block needs to have the multiplexing operation added through modification of the ''work()'' function. Add the following code to the ''work()'' function:
The external interface of the multiplexer is complete. The block's ''work()'' function is modified to add the multiplexing operation. Add the following code to the ''work()'' function:


<pre>if (self.selector):
<pre>if (self.selector):
Line 124: Line 136:




The multiplexer block selects port ''0'' if ''self.selector = True'' and port ''1'' if ''self.selector = False''. The default value of ''self.selector'' is defined in the ''__init()__'' function.
The multiplexer block selects port ''0'' if ''self.selector = True'' and port ''1'' if ''self.selector = False''. The default value of ''self.selector'' is defined in the ''__init__()'' function.


Save the code (CTRL+S) and return to GRC. The ''Multiplexer'' block will now have a message port ''selectPort'':
Save the code (CTRL+S) and return to GRC. The ''Multiplexer'' block has a message port ''selectPort'':


[[File:CompletedMultiplexerBlock.png|800px]]
[[File:CompletedMultiplexerBlock.png|800px]]




Run the flowgraph to make sure everything is correct before moving on. As mentioned in the introduction, a message port does not have to be connected for a flowgraph to run. Because the default value of ''self.selector'' is ''True'', the multiplexer's ''work()'' function will select port ''0'' and send that to the output. The ''QT GUI Time Sink'' will display noise:
Run the flowgraph to make sure everything is correct before moving on. As mentioned in the introduction, a message port does not have to be connected for a flowgraph to run. Because the default value of ''self.selector'' is ''True'', the multiplexer's ''work()'' function will select port ''0'' and send that to the output. The ''QT GUI Time Sink'' displays noise:


[[File:MultiplexerNoiseInput.png|700px]]
[[File:MultiplexerNoiseInput.png|700px]]


== Selector Control: Defining The Block ==


== Selector Control: Defining The Block ==
Another ''Embedded Python Block'' is used to count the number of samples it has received and then send a control message to the multiplexer block in order to toggle the selector.


Another ''Embedded Python Block'' will be used to count the number of samples it has received and then transmit a control message to the multiplexer block in order to toggle the selector.
Start by adding a new ''Python Block'' to the flowgraph, in between the ''Multiplexer'' and ''Throttle''.


Start by adding a ''Python Block'' to the flowgraph, in betweeen the ''Multiplexer'' and ''Throttle'':
Warning! Drag and drop a <u>NEW</u> ''Python Block'' from the block library! Do not copy and paste the existing ''Multiplexer'' block, it only creates a second copy of that block.


[[File:AddPythonBlockToFlowgraph.png|800px]]
[[File:AddPythonBlockToFlowgraph.png|800px]]




Edit the code for the ''Embedded Python Block'' by changing the parameter ''example_param'' in the ''__init()__'' function:
Edit the code for the ''Embedded Python Block''. Change the parameter ''example_param'' in the ''__init__()'' function:


<pre>def __init__(self, Num_Samples_To_Count=128):</pre>
<pre>def __init__(self, Num_Samples_To_Count=128):</pre>
Line 155: Line 168:
<pre>self.Num_Samples_To_Count = Num_Samples_To_Count</pre>
<pre>self.Num_Samples_To_Count = Num_Samples_To_Count</pre>


And remove the ''example_param'' multiplication in the ''work()'' function:
Remove the ''example_param'' multiplication in the ''work()'' function:


[[File:ReplaceExampleParamWithNumSamples.png|700px]]
[[File:ReplaceExampleParamWithNumSamples.png|700px]]


== Selector Control: Defining Message Output Port ==
== Selector Control: Defining Message Output Port ==
Line 165: Line 177:
<pre>import pmt</pre>
<pre>import pmt</pre>


Create a variable ''self.portName'' in the ''__init()__'' function which will be the name of the output port as a string, ''messageOutput'':
Create a variable (''self.portName'') in the ''__init__()'' function containing the name of the output port as a string, ''messageOutput'':
<pre>self.portName = 'messageOutput'</pre>
<pre>self.portName = 'messageOutput'</pre>




An message port is created, or ''registered'', by adding the line in the ''__init()__''function:
A message port is created, or ''registered'', by adding the following line in the ''__init__()''function:
<pre>self.message_port_register_out(pmt.intern(self.portName))</pre>
<pre>self.message_port_register_out(pmt.intern(self.portName))</pre>


Line 175: Line 187:




Save the code (CTRL + S) and return to GRC. The ''Selector Control'' block will now have a message output port:
Save the code and return to GRC. The ''Selector Control'' block has a message output port:


[[File:MessageOutputSelectorControlBlock.png|800px]]
[[File:MessageOutputSelectorControlBlock.png|800px]]
Line 183: Line 195:
A message handler does not need to be defined for an output port. However, the ''work()'' function needs to be modified to create the logic for sending messages.
A message handler does not need to be defined for an output port. However, the ''work()'' function needs to be modified to create the logic for sending messages.


Start by creating two variables in ''__init()__'':
Start by creating two variables in ''__init__()'':


<pre>self.state = True
<pre>self.state = True
Line 208: Line 220:




Save the code and return to GRC. Enter ''128'' for ''Num_Samples_To_Count'' in the properties of the ''Selector Control'' block:
Save the code and return to GRC. Enter ''32000'' for ''Num_Samples_To_Count'' in the properties of the ''Selector Control'' block:


[[File:SelectorControlProperties.png|500px]]
[[File:SelectorControlProperties.png|500px]]




Add the ''Message Debug'' block and connect the ''messageOutput'' port to it. Running the flowgraph will show the messages are being sent, alternating between ''#t'' (True) and ''#f'' (False):
Add the ''Message Debug'' block and connect the ''messageOutput'' port to the "print" input port. Running the flowgraph shows the messages are being sent at a rate of once a second, alternating between ''#t'' (True) and ''#f'' (False):


[[File:MessageDebugExample.png|800px]]
[[File:MessageDebugExample.png|800px]]


However, the output message port from ''Selector Control'' is not yet connected to the input message port of ''Multiplexer'' so the ''QT GUI Time Sink'' will only show noise.
However, the output message port from ''Selector Control'' is not yet connected to the input message port of ''Multiplexer'' so the ''QT GUI Time Sink'' only shows noise.
 
== Final Flowgraph ==


Connect the output message port of ''Selector Control'' to the input message port of ''Multiplexer''. Notice that the dashed line travels behind the two blocks and can be difficult to see:


[[File:ConnectMessagePorts.png|800px]]


''Virtual Sinks'' and ''Virtual Sources'' can be used to clean up some of the connections and make the flowgraph easier to understand. Click on the dashed line and delete it. Drag and drop a ''Virtual Sink'' and ''Virtual Source'' into the workspace. Change the ''Stream ID'' to ''message'' for both the ''Virtual Sink'' and ''Virtual Source'', and then connect them in the flowgraph:


[[File:MessageVirtualSourceSink.png|800px]]




Run the flowgraph. The ''QT GUI Time Sink'' shows an alternating output between the ''Noise Source'' and ''Signal Source'':


[[File:AlternatingOutputTimeSink.png|800px]]


The next tutorial, [[Low_Pass_Filter_Example|Low Pass Filter Example]], demonstrates how to use filtering blocks in GNU Radio.
The next tutorial, [[Python_Block_Tags|Python Block Tags]], describes how to read and write tags in a Python block.

Latest revision as of 15:35, 11 July 2024

Beginner Tutorials

Introducing GNU Radio

  1. What is GNU Radio?
  2. Installing GNU Radio
  3. Your First Flowgraph

Flowgraph Fundamentals

  1. Python Variables in GRC
  2. Variables in Flowgraphs
  3. Runtime Updating Variables
  4. Signal Data Types
  5. Converting Data Types
  6. Packing Bits
  7. Streams and Vectors
  8. Hier Blocks and Parameters

Creating and Modifying Python Blocks

  1. Creating Your First Block
  2. Python Block With Vectors
  3. Python Block Message Passing
  4. Python Block Tags

DSP Blocks

  1. Low Pass Filter Example
  2. Designing Filter Taps
  3. Sample Rate Change
  4. Frequency Shifting
  5. Reading and Writing Binary Files

SDR Hardware

  1. RTL-SDR FM Receiver
  2. B200-B205mini FM Receiver

This tutorial describes how to read and write messages using the Embedded Python Block.

The previous tutorial, Python Block with Vectors, demonstrates how to write an Embedded Python Block with vector inputs and outputs. The next tutorial, Python Block Tags, describes how to read and write tags in a Python block.

Message Overview

Messages are an asynchronous way to send information between blocks. Messages are good at conveying control data, maintaining a consistent state across blocks and providing some forms of non-data feedback to blocks in a flowgraph.

Messages have a couple of unique properties:

  • There is no sample-clock based guarantee when messages will arrive
  • Messages are not associated with a specific sample like a tag
  • Message input and output ports do not have to be connected in GRC
  • Message ports use the Polymorphic Type (PMT)

Message ports are denoted by a grey color and their connections are distinguished by dashed lines:

MessageBlockExample.png

More information on message passing with PMTs can be found here: Message Passing

Flowgraph Overview

The following flowgraph demonstrates how to:

  • Add message sending and receiving ports to Python blocks
  • Send messages
  • Receive and handle messages
  • Adapt block behavior in the work() function based on a received messages

Two custom Embedded Python Blocks are to be created to:

  • Select, or multiplex, one of two input signals based on a received message
  • Count the number of samples and send a message to the multiplexing block to switch inputs

This tutorial assumes you have already created at least one Embedded Python Block. If not, please complete the tutorial Creating Your First Block before moving on.

Start by adding the following blocks to the flowgraph and connecting them:

  • Noise Source
  • Signal Source
  • Python Block
  • Throttle
  • QT GUI Time Sink

StartingFlowgraphMessagePassing.png

Multiplexer: Defining The Block

Double-click the Embedded Python Block and open the source in the editor:

EditMultiplexerProperties.png


The example_param is not needed, so remove the variable example_param from the signature of the __init__() function:

def __init__(self):  # only default arguments here

and delete the line:

self.example_param = example_param

Change the name of the block to Multiplexer:

name='Multiplexer',

Add a second input to the block:

in_sig=[np.complex64, np.complex64],

Delete the multiplication by example_param:

output_items[0][:] = input_items[0]

When all of these changes are made, the code should look as follows:

AddSecondInputEmbeddedPythonBlock.png


Recall that Python requires the proper indentation. By default, the Embedded Python Block uses indentation of in multiples of 4 spaces. Mixing tabs and spaces raises a syntax error:

TabsSpacesError.png


Save the code and return back to GRC. Note how the name of the block has changed and the block now has two inputs. Connect the Noise Source and Signal Source to the two inputs:

MultiplexerWithTwoInputs.png

Multiplexer: Defining Message Input Port

Return to the code editor. An input message port needs to be added. Create a variable to store the message port name:

self.selectPortName = 'selectPort'

Add a line to create, or register, the message input port:

self.message_port_register_in(pmt.intern(self.selectPortName))

Add a line to connect the input port with a message handler.

self.set_msg_handler(pmt.intern(self.selectPortName), self.handle_msg)

AddMessageHandler.png


Save the code. Notice that errors are listed in the properties of the Embedded Python Block:

CodeEditorErrorExample.png


This error says that the pmt library needs to be imported. Return to the code editor and add the proper import statement:

import pmt

ImportPMT.png

Multiplexer: Creating Message Handler

A message handler is the function which is called when a message is received.

The message handler function has to be defined. This message handler switches between the two input ports based on the received message. The received message is a Boolean that is True or False. Define a new variable under __init__() which is the input selector,

self.selector = True

Define the handle_msg() function:

def handle_msg(self, msg):
    self.selector = pmt.to_bool(msg)

The function pmt.to_bool() takes the message PMT and then converts the data type into Python's Boolean data type. PMTs are used in message passing to abstract data types. For example, messages can be used to send and receive strings, floats, integers and even lists. More information about PMTs can be found on the Polymorphic Types (PMTs) wiki page.

AddSelectorAttribute.png

Multiplexer: Using a Message in work()

The external interface of the multiplexer is complete. The block's work() function is modified to add the multiplexing operation. Add the following code to the work() function:

if (self.selector):
    output_items[0][:] = input_items[0]
else:
    output_items[0][:] = input_items[1]

MultiplexerWorkFunction.png


The multiplexer block selects port 0 if self.selector = True and port 1 if self.selector = False. The default value of self.selector is defined in the __init__() function.

Save the code (CTRL+S) and return to GRC. The Multiplexer block has a message port selectPort:

CompletedMultiplexerBlock.png


Run the flowgraph to make sure everything is correct before moving on. As mentioned in the introduction, a message port does not have to be connected for a flowgraph to run. Because the default value of self.selector is True, the multiplexer's work() function will select port 0 and send that to the output. The QT GUI Time Sink displays noise:

MultiplexerNoiseInput.png

Selector Control: Defining The Block

Another Embedded Python Block is used to count the number of samples it has received and then send a control message to the multiplexer block in order to toggle the selector.

Start by adding a new Python Block to the flowgraph, in between the Multiplexer and Throttle.

Warning! Drag and drop a NEW Python Block from the block library! Do not copy and paste the existing Multiplexer block, it only creates a second copy of that block.

AddPythonBlockToFlowgraph.png


Edit the code for the Embedded Python Block. Change the parameter example_param in the __init__() function:

def __init__(self, Num_Samples_To_Count=128):

Change the name of the block:

name='Selector Control',

Store the Num_Samples_To_Count as a private variable:

self.Num_Samples_To_Count = Num_Samples_To_Count

Remove the example_param multiplication in the work() function:

ReplaceExampleParamWithNumSamples.png

Selector Control: Defining Message Output Port

Import the pmt library:

import pmt

Create a variable (self.portName) in the __init__() function containing the name of the output port as a string, messageOutput:

self.portName = 'messageOutput'


A message port is created, or registered, by adding the following line in the __init__()function:

self.message_port_register_out(pmt.intern(self.portName))

AddControlSelectorMessageOutput.png


Save the code and return to GRC. The Selector Control block has a message output port:

MessageOutputSelectorControlBlock.png

Selector Control: Sending a message in work()

A message handler does not need to be defined for an output port. However, the work() function needs to be modified to create the logic for sending messages.

Start by creating two variables in __init__():

self.state = True
self.counter = 0

Add the line to increase the number of counted samples for each call to work():

self.counter = self.counter + len(output_items[0])

DefineCounterInBlock.png


Add the logic to send a message once the counter is exceeded:

if (self.counter > self.Num_Samples_To_Count):
    PMT_msg = pmt.from_bool(self.state)
    self.message_port_pub(pmt.intern(self.portName), PMT_msg)
    self.state = not(self.state)
    self.counter = 0

The logic translates the Python Boolean data type of self.state into a PMT using the pmt.from_bool() function call and then sends, or publishes, the message on the output message port. The self.state variable is toggled to its opposite value and the counter is reset.

CompletedSelectorControlBlock.png


Save the code and return to GRC. Enter 32000 for Num_Samples_To_Count in the properties of the Selector Control block:

SelectorControlProperties.png


Add the Message Debug block and connect the messageOutput port to the "print" input port. Running the flowgraph shows the messages are being sent at a rate of once a second, alternating between #t (True) and #f (False):

MessageDebugExample.png

However, the output message port from Selector Control is not yet connected to the input message port of Multiplexer so the QT GUI Time Sink only shows noise.

Final Flowgraph

Connect the output message port of Selector Control to the input message port of Multiplexer. Notice that the dashed line travels behind the two blocks and can be difficult to see:

ConnectMessagePorts.png

Virtual Sinks and Virtual Sources can be used to clean up some of the connections and make the flowgraph easier to understand. Click on the dashed line and delete it. Drag and drop a Virtual Sink and Virtual Source into the workspace. Change the Stream ID to message for both the Virtual Sink and Virtual Source, and then connect them in the flowgraph:

MessageVirtualSourceSink.png


Run the flowgraph. The QT GUI Time Sink shows an alternating output between the Noise Source and Signal Source:

AlternatingOutputTimeSink.png

The next tutorial, Python Block Tags, describes how to read and write tags in a Python block.