Blog

QT GUI Widgets with Message Passing

 Just today, I issued a Pull Request for a new feature in GNU Radio. This adds a new form of what we already called the QT GUI Entry widget. That widget provides a simple QLineEdit box where we type in values, they get stored as a Python variable, and we pass these variables around like normal GRC variables. When running, updates to the entry box are propagated to anything using that variable in the flowgraph.

We're trying to move beyond this world where everything is in a Python file that's assumed to be completely local. Instead, we want to move to a world where we can use the message passing architecture to manage data and settings. So when we have a message interface to a block, we might want to post data or update a value through that message interface from a UI somehow. This leads us to the possibility that the UI is a separate window, application, or even machine from the running flowgraph. We have already made strides in this direction by adding the postMessage interface to ControlPort, which I spoke about in the last post on New Way of Using ContorlPort. However, ControlPort can take some time to set up the application, craft the UI, and make a working product. Aside from that method, we wanted to have easy ways within GRC to build applications that allow us to pass and manage messages easily. Hence the new QTGUI Message Edit Box (qtgui_edit_box_msg).

 

This is an flowgraph example that now comes with GNU Radio's gr-qtgui (test_qtgui_msg.grc). This shows the use of three of the new message edit boxes. In the upper part of the graph, we have the edit box controlling the center frequency of a Waterfall and Frequency Sink. These sinks can take in a message that's a PMT pair in the form ( "freq" <float frequency> ). So the edit box has a setting called Pair that sets this up to handle the PMT pair messages. It's actually the default since we'll be using the key:value pair concept a lot to manage message control interfaces. When the edit box is updated and we press enter, it publishes a message and the two GUI sinks are subscribed to them, so they get the message, parse it, and update their center frequency values accordingly.

Now the flip side of this is that the edit boxes also have an input message port. This allows us to programmatically set the value inside the box. The two GUI sinks have the ability to have a user adjust their center frequency. When a user double-clicks on a frequency in the plot, the block sets that value as the center frequency and then publishes a message (in the same key:value pair where key="freq"). This means that not only is the widget we just double-clicked updated, anything listening for that message is updated. So the edit box is kept in sync with the change in the GUI. Now, when the new data received is different than what was in that edit box to begin with, the box re-emits that message. So now, say we double-clicked on the Frequency Sink near the 10 kHz frequency line. That message is propagated not only to the edit box, but the message also gets sent from that box through to the waterfall sink. Now all of the widgets are kept in sync. And because the re-posting of the message only happens when a change occurs, we prevent continuous loops. Here's the output after the double-clicking:

Both GUI display widgets have the same center frequency, and that value is also shown in the Frequency edit box above. Because it's using the pair concept, we have two boxes, the left for the key and right for the value.

This example has two other edit boxes that show up at the bottom of the GUI. This is to allow us to easily test other properties and data types. They are connected in a loop, so when one is changed, the other immediately follows. Note here that we are not using the pair concept but that the data type is complex. To make it easy on us, we use a Boost lexical_cast, which means we use the format "(a,b)" for the real and imaginary parts. All of this, by the way, is explained in the blocks'd documentation.

Now, we have the ability to pass messages over ZMQ interfaces. Which means we can create flowgraphs on one side of the ZMQ link that just handle GUI stuff. On the other side, we use ZMQ sources to pass messages around the remotely running flowgraph. Pretty cool and now very easy to do in GRC.

This is just one way of handling QT widgets as message passing blocks. Pretty easy and universal for sending messages with data we're used to. But it's just a start and a good template for other future widgets to have similar capabilities, like with range/sliders, check boxes, combo boxes, etc. All of these should be able to pretty easily use this as a template with different GUI widgets for manipulating the actual data.

New Way of Using ControlPort

We've started down the path of providing more capabilities with ControlPort. Specifically, I'd like to get us to have more blocks within the GNU Radio main source code that support configuration over ControlPort. We are going to rely heavily on ControlPort for our Android work where the Java application space will talk to and control the running GNU Radio flowgraph almost solely over ControlPort.

One of the main things missing from our ControlPort capabilities here is the ability to control the settings of a UHD device. The main reason is that the generic interface for ControlPort comes in two forms:

  • <type> getter_method(void);
  • void setter_method(<value type> <value>);

This interface does not work for the UHD interface for most of the important parameters. For example, setting the frequency of a USRP source looks like:

tune_result_t set_center_freq(double freq, size_t chan)

So it takes two arguments and returns a data structure. This kind of thing isn't easily encompassed in ControlPort's generic interfaces.

However, instead of trying to expand the interfaces for all of these types of commands, we have one more general format interface that we can use for lots of these things, the message handler, which looks like:

  • void message_handler_function_name(pmt::pmt_t message);

It just makes sense to make use of these already-available message handling functions in blocks to handle setting values over ControlPort as well. So I've recently added a new interface to our ControlPort system called "postMessage". This function takes three arguments:

  • alias: the block's alias in the system as a serialized PMT string.
  • port: the port name of the block's message handler as a serialized PMT string.
  • msg: the actual message to be passed to the message handler as a serialized PMT.

The msg PMT can be any form of a PMT that would be created especially for the type of message expected by the message handler. All of this can be easily constructed in Python or any other supported language. In fact, in Python, we use our RPCConnection abstracted interface to take these values in as a (string alias, string port, PMT message) -- internally, these are serialized and posted to ControlPort. For example, if we have a copy block that we want to enable the copy function, we would use the following postMessage to send to block named "copy0" on the enable message port "en" with a PMT True value.

radioclient.postMessage("copy0", "en", pmt.PMT_T)

Let's take a more complicated example where we are trying to change the frequency of a USRP source. First, we have to know the alias of the USRP source in the flowgraph. As an aside, I'm hoping to provide some discovery mechanisms for available handlers exported through ControlPort soon. In our case, let's assume the alias is 'usrp_source0,' and we know from the manual that it has a port named 'command' that takes a key:value pair. One command key is 'freq' to set the frequency based on a floating point value. We've already created the ControlPort radio interface, called 'radio' here, so we just need to do:

radio.postMessage('usrp_source0', 'command',
  pmt.cons(pmt.intern('freq'), pmt.from_double(101e6)))

The only tricky thing here is to know that the command takes a PMT pair of a string and the correct value. The pmt.cons constructs a pair with the 'freq' as the first part and a double PMT for 101 MHz. This would get posted to the USRP devices to change the channel over our remote ControlPort interface.

Examples

I have added a couple of simple examples to GNU Radio to exercise this new feature. First is the simple_copy.grc file that runs a graph that has a copy block. We can use the simple_copy_controller.py program to toggle the copy block's enable port either True or False to start and stop data flowing.

The second and more exciting example is the usrp_source_control.grc. This just runs a flowgraph where data is sourced from a USRP device and plotted in time and frequency. It is set up with a sample rate of 1 MHz, but no gain, frequency, or antenna are selected as shown below.

initial_grc_setup.png

We expect to see nothing since we have no tuning or gain set, and in my case, there is no antenna connected to the default RX2 port. In the running flowgraph, we've provided no application controls for this like QTGUI slider or chooser widgets.

But since we have a ControlPort endpoint available on localhost port 9090, we can control it remotely. In this case, I'm using the program on the same machine and talking over the localhost interface.

I've used the usrp_source_controller.py application to issue ControlPort commands that will post a message to set the frequency, gain, antenna port, and sample rate to the USRP device allowing me to see the signal now shown above.

This new interface should prove to be quite powerful in controlling GNU Radio flowgraphs over ControlPort. It will also force us to expand the available message handling support on many blocks that we will be wanting to manipulate over ControlPort and in Android apps. The use of the message passing infrastructure also means we have a lot more flexibility in structuring our ControlPort commands.