8 minute read

The Control Plane is in charge of creating a flow graph according to the configuration and then managing the processing blocks. It consists of four main components:

  • A configuration mechanism, flexible enough to allow for an indeterminate number of algorithms, implementations, associated parameters, and use cases;
  • A GNSS block factory encapsulating the complexity involved in the creation of processing blocks and referring to the newly created object through a common interface, thus allowing the addition of new blocks without changing a single line of code of the client software that makes use of it;
  • A GNSS flow graph managing the creation of the processing blocks according to the configuration and connecting them together in a process network implementing the software receiver; and
  • A Control Thread managing the whole thing.

Hereafter, we describe how those components are implemented in GNSS-SDR.

The configuration mechanism

Configuration allows users to define in an easy way their own custom receiver by specifying the flow graph (the type of signal source, number of channels, algorithms to be used for each channel and each module, strategies for satellite selection, type of output format, etc.). Since it is difficult to foresee what future module implementations will be needed in terms of configuration, we used a very simple approach that can be extended without a major impact on the code. This can be achieved by simply mapping the names of the variables in the processing blocks with the names of the parameters in the configuration.

Properties are passed around within the program using the ConfigurationInterface class. There are two implementations of this interface: FileConfiguration and InMemoryConfiguration. A FileConfiguration object reads the properties (pairs of property name and value) from a file and stores them internally. On the contrary, InMemoryConfiguration does not read from a file; it remains empty after instantiation, and property values and names are set using the set_property method. FileConfiguration is intended to be used in the actual GNSS-SDR application whereas InMemoryConfiguration is intended to be used in tests to avoid file-dependency in the file system.

Classes that need to read configuration parameters will receive instances of ConfigurationInterface from where they will fetch the values. For instance, parameters related to SignalSource should look like this:

SignalSource.parameter1=value1
SignalSource.parameter2=value2

The name of these parameters can be anything but one reserved word: implementation. This parameter indicates in its value the name of the class that has to be instantiated by the factory for that role. For instance, if we want to use the implementation Pass_Through for module SignalConditioner, the corresponding line in the configuration file would be

SignalConditioner.implementation=Pass_Through

Since the configuration is just a set of property names and values without any meaning or syntax, the system is very versatile and easily extendable. Adding new properties to the system only implies modifications in the classes that will make use of these properties. In addition, the configuration files are not checked against any strict syntax so it is always in a correct status (as long as it contains pairs of property names and values in INI format. An INI file is an \(8\)-bit text file in which every property has a name and a value, in the form name = value. Properties are case-insensitive, and cannot contain spacing characters. Semicolons (;) indicate the start of a comment; everything between the semicolon and the end of the line is ignored.

; THIS IS A COMMENT
SignalConditioner.implementation=Pass_Through ; THIS IS ANOTHER COMMENT

In this way, a full GNSS receiver can be uniquely defined in one text file in INI format.

$ gnss-sdr --config_file=/path/to/my_receiver.conf

GNSS-SDR allows the user to define a custom GNSS receiver, including its architecture (number of bands, channels per band, and targeted signal) and the specific algorithms and parameters for each of the processing blocks through a single configuration file (a simple text file in INI format). Thus, each configuration file defines a different GNSS receiver. Some examples of such files are available at gnss-sdr/conf.

The GNSS Block Factory

Hence, the application defines a simple accessor class to fetch the configuration pairs of values and passes them to a factory class called GNSSBlockFactory. This factory decides, according to the configuration, which class needs to be instantiated and which parameters should be passed to the constructor. Hence, the factory encapsulates the complexity of blocks’ instantiation. With that approach, adding a new block that requires new parameters will be as simple as adding the block class and modifying the factory to be able to instantiate it. This loose coupling between the blocks’ implementations and the syntax of the configuration enables extending the application capacities to a high degree. It also allows producing fully customized receivers (for instance, a testbed for acquisition algorithms), and to place observers at any point of the receiver chain.

Design pattern A Factory encapsulates the complexity of the instantiation of processing blocks.

This scheme is known as the Factory Method design pattern1. As shown in the figure above, this pattern encapsulates the processes involved in the creation of objects by defining an interface for creating an object, but letting subclasses decide which class to instantiate.

The GNSS Flow Graph

The GNSSFlowgraph class is responsible for preparing the graph of blocks according to the configuration, running it, modifying it during run-time, and stopping it. Blocks are identified by their role. This class knows which roles it has to instantiate, and how to connect them to configure the generic graph that is shown in the figure below. It relies on the configuration to get the correct instances of the roles it needs, and then it applies the connections between GNU Radio blocks to make the graph ready to be started. The complexity related to managing the blocks and the data stream is handled by GNU Radio’s gr::top_block class. GNSSFlowgraph wraps the gr::top_block instance so we can take advantage of the GNSS block factory, the configuration system, and the processing blocks. This class is also responsible for applying changes to the configuration of the flow graph during run-time, dynamically reconfiguring channels: it selects the strategy for selecting satellites. This can range from a sequential search over all the satellites’ ID to smarter approaches that determine what are the satellites most likely in-view based on rough estimations of the receiver position in order to avoid searching satellites on the other side of the Earth.

This class internally codifies actions to be taken on the graph. These actions are identified by simple integers. GNSSFlowgraph offers a method that receives an integer that codifies an action, and this method triggers the action represented by the integer. Actions can range from changing internal variables of blocks to modifying completely the constructed graph by adding/removing blocks. The number and complexity of actions is only constrained by the number of integers available to make the codification. This approach encapsulates the complexity of preparing a complete graph with all necessary blocks instantiated and connected. It also makes good use of the configuration system and of the GNSS block factory, which keeps the code clean and easy to understand. It also enables updating the set of actions to be performed to the graph quite easily.

The Control Thread

The ControlThread class is responsible for instantiating the GNSSFlowgraph and passing the required configuration. Once the flow graph is defined and its blocks connected, it starts to process the incoming data stream. The ControlThread object is then in charge of reading the control queue and processing all the messages sent by the processing blocks via a thread-safe message queue.

Configuration pattern The Control Thread reads the configuration and builds the flow graph of signal processing blocks that defines the receiver.

As we saw in the Overview, the main method of GNSS-SDR instantiates an object of the class ControlThread, managed by a smart pointer:

auto control_thread = std::make_unique<ControlThread>();

The constructor of this object reads the command-line flag provided by the user when executing the receiver which points to the text file containing the configuration, as shown above:

$ gnss-sdr --config_file=/path/to/my_receiver.conf

Then, when the run() method of the control_thread object is called, a member of class GNSSFlowgraph connects the flow graph, starts the flow of data from sources to sinks, and keeps processing messages from a control queue until the receiver stops.

An excerpt of its actual implementation is as follows, where flowgraph_ is an object of the class GNSSFlowgraph:

int ControlThread::run()
{
    // Connect the flowgraph
    flowgraph_->connect();

    // Start the flowgraph
    flowgraph_->start();

    // Launch the GNSS assistance process
    assist_GNSS();

    // Main loop to read and process the control messages
    while (flowgraph_->running() && !stop_)
        {
            read_control_messages();
            if (control_messages_ != 0) process_control_messages();
        }
    std::cout << "Stopping GNSS-SDR, please wait!\n";
    flowgraph_->stop();
    flowgraph_->disconnect();
    return 0;
  }

Hence, the object of class GNSSFlowgraph will parse the configuration file and will ask the Block Factory for the corresponding Signal Source, Signal Conditioner, Channels (each one with its own Acquisition, Tracking, and Telemetry Decoder), one Observables block (collecting the processing results from all Channels), and a PVT block (acting as a signal sink):

Basic GNSS flow graph Diagram of a basic (single-band, single-system) flow graph generated by GNSSFlowgraph.

Please check out My first position fix for an example of such receiver’s flow graph configuration file.

GNSS-SDR’s configuration mechanism is flexible enough for allowing other more complex flow graphs. For instance, you can target a given signal (for instance, GPS L1 C/A) with eight channels, and define eight more channels targeting Galileo E1 B/C signals, thus defining a multi-system receiver. Or maybe extend that structure to another band, defining a flow graph for a multi-system, dual-band GNSS receiver:

Complex GNSS flow graph Diagram of a multi-band, multi-system flow graph generated by GNSSFlowgraph.

The next section describes the available implementations for each of the available GNSS-SDR processing blocks and how they are configured.


References

  1. C. Fernández-Prades, C. Avilés, L. Esteve, J. Arribas and P. Closas, Design patterns for GNSS software receivers, in Proc. of the 5th ESA Workshop on Satellite Navigation Technologies, ESTEC, Noordwijk, The Netherlands, Dec. 2010, pp. 1 - 8. 

Updated:

Leave a comment