GNU Radio 3.7.1 C++ API
ControlPort

Introduction

This is the ControlPort package. It is a tool to create distributed control applications for GNU Radio. It provides blocks that can be connected to an output stream to plot the signal remotely. It also provides an API that allows blocks to export variables that can be set, monitored, and plotted remotely.

ControlPort-specific functions and utilities are found in the 'ctrlport' namespace. From Python, access is done using the gnuradio.ctrlport module, imported as:

    from gnuradio import ctrlport

Dependencies

ControlPort is an abstracted remote procedure call tool that. It is built on top of other middleware libraries. Currently, the only implemented middleware library is the Internet Communication Engine (ICE). It is possible to replace this library with other similar tools in such a way that the GNU Radio interface to ControlPort is unchanged. However, most clients were purpose-built around ICE and will need to be redone.

ControlPort requires ZeroC's ICE and associated libraries/headers/programs. ICE is generally installed into the standard paths if using a software repo (like apt-get, yum, etc.). If installed by hand, GNU Radio assumes ICE is installed into /opt/Ice-3.4.2. If this is not the case, you can tell GNU Radio where to find ICE by passing to cmake the following:

-DICE_MANUAL_INSTALL_PATH=<your path here>

Configuration

ControlPort is configured using two files. The first is the GNU Radio preferences file while the second file is specific to the type of middleware used. Since we are focusing on using ICE, the configuration file uses the ICE configuration file and format.

The GNU Radio preferences file has three options. The 'on' option is used to enable or disable the use of ControlPort, and is disabled by default. The 'config' option allows a user to specify the middleware-specific configuration file. The 'edges_list' is a special option that exports the list of nodes and edges of the flowgraph across ControlPort. This latter option is mainly used for redrawing the flowgraph for the Performance Counter applications.

  [ControlPort]
  on = True
  config = ctrlport.conf
  edges_list = True

The ControlPort preferences are installed by default into 'gnuradio-runtime.conf'. These can always be overridden in the local ~/.gnuradio/config.conf file.

En example ICE configuration file is installed as $prefix/etc/gnuradio/ctrlport.conf.example. More information on how to configure ICE can be found here: http://doc.zeroc.com/display/Ice/Properties+and+Configuration

Using ControlPort to Export Variables

The ability to export variables from a block is inherited from gr::block. Then, when the flowgraph is started, the function setup_rpc() is called in turn for each block. By default, this is an empty function. A block overloads this function and defines and exports variables in it.

Say we have a class gr::blocks::foo that has variables a and b that we want to export. Specifically, we want to be able to read the values of both a and b and also set the value of b. The class gr::blocks::foo has setters and getters all set up. So our class implementation header file looks something like:

namespace gr {
  namespace blocks {

    class foo_impl : public foo
    {
    private:
      float  d_a, d_b;

    public:
      foo_impl(float a, float b);
      ~foo_impl();

      float a() const { return d_a; }
      float b() const { return d_a; }
      void set_a(float a) { d_a = a; }
      void set_b(float b) { d_b = b; }
      void setup_rpc();
      int work(int noutput_items,
               gr_vector_const_void_star &input_items,
               gr_vector_void_star &output_items);
    };

  } /* namespace blocks */
} /* namespace gr */

The source code then sets up the class and fills in setup_rpc().

namespace gr {
  namespace blocks {

    foo_impl::foo_impl(float a, float b):
      sync_bloc(....),
      d_a(a), d_b(b)
    { }
    
    foo_impl::~foo_impl()
    { }

    void
    foo_impl::setup_rpc()
    {
#ifdef GR_CTRLPORT
      add_rpc_variable(
        rpcbasic_sptr(new rpcbasic_register_get<foo, float>(
          alias(), "a",
          &foo::a,
          pmt::mp(-2.0f), pmt::mp(2.0f), pmt::mp(0.0f),
          "", "Get value of a", RPC_PRIVLVL_MIN,
          DISPTIME | DISPOPTSTRIP)));

      add_rpc_variable(
        rpcbasic_sptr(new rpcbasic_register_get<foo, float>(
          alias(), "b",
          &foo::b,
          pmt::mp(0.0f), pmt::mp(20.0f), pmt::mp(10.0f),
          "", "Get value of b", RPC_PRIVLVL_MIN,
          DISPTIME | DISPOPTSTRIP)));

      add_rpc_variable(
        rpcbasic_sptr(new rpcbasic_register_set<foo, float>(
          alias(), "b",
          &foo::set_b,
          pmt::mp(0.0f), pmt::mp(20.0f), pmt::mp(10.0f),
          "", "Set value of b", RPC_PRIVLVL_MIN,
          DISPNULL)));
#endif /* GR_CTRLPORT */
    }

    int
    foo_impl::work(int noutput_items,
                   gr_vector_const_void_star &input_items,
                   gr_vector_void_star &output_items)
    { .... }

  } /* namespace blocks */
} /* namespace gr */

In the above example, we're ignoring some of the basic semantics of the class as a GNU Radio block and focus just on the call to set up the get and set functions over ControlPort. Each block has a function that allows us to add a new ControlPort interface object to a list, the add_rpc_variable. We don't care about that list anymore; that's for ControlPort to worry about. We just add new variables, either setters or getters.

Without dissecting every piece of the above calls, notice that we use the public class, gr::blocks::foo as the class, not the implementation class. We also use the block's alias, which GNU Radio uses as a database entry to connect a block by name to the pointer in memory. This allows ControlPort to know where the object in memory is at any given time to access the setters and getters.

The three PMTs specified are simply an expected minimum, maximum, and default value. None of these are strictly enforced and only serve as guides. The RPC_PRIVLVL_MIN is currently a placeholder for a privilege level setting. In many cases, reading b might be fine for everyone, but we want strong restrictions on who has the ability to set b.

And finally, we can specify display options to hint at the right way to display this variable when remotely plotting it. More on that in the following section.

Finally, note that we put #ifdefs around the code. We always want setup_rpc to be there and callable, but if ControlPort was not built for GNU Radio, we cannot register any variables with it. This is just a nicety to allow us to set up our code for use with ControlPort without requiring it.

Alternative Registers

If using the concept above, setup_rpc automatically gets called when the flowgraph is started. In most instances, this is all we ever need since there's nothing interesting going on until then. However, if not using a gr::block or needing access before we run the flowgraph, the above method won't work (it comes down to when the block's alias has meaning).

There are alternate variable registration functions for the sets and gets. These take the form:

  rpcbasic_register_get(const std::string& name,
                        const char* functionbase,
                        T* obj,
                        Tfrom (T::*function)(), 
                        const pmt::pmt_t &min, const pmt::pmt_t &max, const pmt::pmt_t &def,
                        const char* units_ = "", 
                        const char* desc_ = "",
                        priv_lvl_t minpriv_ = RPC_PRIVLVL_MIN,
                        DisplayType display_ = DISPNULL)

  rpcbasic_register_set(const std::string& name,
                        const char* functionbase,
                        T* obj,
                        void (T::*function)(Tto), 
                        const pmt::pmt_t &min, const pmt::pmt_t &max, const pmt::pmt_t &def,
                        const char* units_ = "", 
                        const char* desc_ = "",
                        priv_lvl_t minpriv_ = RPC_PRIVLVL_MIN,
                        DisplayType display_ = DISPNULL)

The only thing different about the above code is that instead of taking a single 'alias()' name, which provides us access to the objects pointer, we instead provide a unique name (fucntionbase) and a pointer to the object itself (obj). These are templated functions, so the class T is known from that.

If using this method, the recommended way is to create a new function (not setup_rpc), register the variable using add_rpc_variable but with the different register_get/set shown here, and then call this function either in the object's constructor or make it a public member function to be called when you need it.

Display Options

When exporting a new RPC variable over ControlPort, one argument is a display options mask. These options are useful to a remote client to tell identify activities like default plotters and initial conditions. The gr-ctrlport-monitor application uses this heavily in determining how to plot ControlPort variables.

The options mask is just a 32-bit value with options OR'd together. Certain options are only appropriate for certain types of plots. Options on plots where that option is not available will simply be ignored.

The main caveat to be aware of is that the DISPXY plot type is specific to complex values. Therefore, DISPOPTCPLX is assumed.

These options are specified in rpccallbackregister_base.h and are exposed through SWIG to live in the gr namespace.

Plot Types

  • DISPNULL: Nothing specified.
  • DISPTIME: Time-domain plot.
  • DISPXY: XY or constellation plot (complex only).
  • DISPPSD: PSD plot.
  • DISPSPEC: Spectrogram plot.
  • DISPRAST: Time raster plot (non-complex only)

Plot Options

  • DISPOPTCPLX: Signal is complex.
  • DISPOPTLOG: Start plot in semilog-y mode (time domain only).
  • DISPOPTSTEM: Start plot in stem mode (time domain only).
  • DISPOPTSTRIP: Run plot as a stripchart (time domain only).
  • DISPOPTSCATTER: Do scatter plot instead of lines (XY plot only).

ControlPort Probes

ControlPort provides a set of probes that can be used as sinks that pass vectors of data across ControlPort. These probes are used to sample or visualize data remotely. We can place a ControlPort probe anywhere in the flowgraph to grab the latest sample of data from the block it's connected to.

The main ControlPort probe to use is blocks.ctrlport_probe2_x. From GRC, this is simply "CtrlPort Probe", which can handle complex, floats, ints, shorts, and bytes. The blocks are named and given a description to identify them over ControlPort. The blocks also take a vector length for how many samples to pass back at a time. Finally, these blocks take a display hint, as described in the above section. This allows us to specify the default behavior for how to display the samples.

Another block that can be used is the fft.ctrlport_probe_psd to calculate the PSD and pass that over the ControlPort interface.

ControlPort Monitors

There are two main ControlPort monitor applications provided with GNU Radio. Both act similarly. The first is a standard ControlPort monitor application. This connects to a running flowgraph and displays all exported interfaces in a table format. The name, unit, latest sample, and description of all interfaces are display in a row. Double-clicking will open up the default display. Right clicking any item will allow the user to select the type of plot to use to display the data.

When a display is active, using the buttons at the top, the subwindows can all be tiled or windowed as needed to manage the full interface. We can then drag-and-drop any other item on top of a currently running display plot.

To launch the ControlPort monitor application, know the IP address and port of the ControlPort endpoint established by the flowgraph and run:

gr-ctrlport-monitor <ip-addr> -p <port>

Performance Monitor

A second application is used to locally redraw the flowgraph and display some of the Performance Counters. In this application, the nodes are blue boxes where the size of the box is proportional to the work time and the color depth and line width are proportional to the output buffer fullness.

The controls at the top of the Performance Monitor application allow us to select the instantaneous, average, and variance values of the Performance Counters. And the work time and buffer fullness can be displayed as a table or bar graph.

To launch the Performance Monitor, run:

gr-perf-monitorx <ip-addr> -p <port>