GNSS-SDR’s main method initializes the logging library, processes the command line flags, if any, provided by the user and instantiates a ControlThread object. Its constructor reads the configuration file, creates a control queue and creates a flowgraph according to the configuration. Then, the program’s main method calls the run() method of the instantiated object, an action that connects the flowgraph and starts running it. After that, and until a stop message is received, it reads control messages sent by the receiver’s modules through a safe-thread queue and processes them. Finally, when a stop message is received, the main method executes the destructor of the ControlThread object, which deallocates memory, does other cleanup and gracefully exits the program.
The Control Plane is in charge of creating a flowgraph according to the configuration and then managing the modules. Configuration allows users to define in an easy way their own custom receiver by specifying the flowgraph (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.).
The program defines an 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 in a high degree. It also allows to produce fully customized receivers, for instance a testbed for acquisition algorithms, and to place observers at any point of the receiver chain.
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 its role. This class knows which roles it has to instantiate and how to connect the. 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 flowgraph 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 in the other side of the Earth.
This class internally codifies actions to be taken on the graph, identified by 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 ControlThread class is responsible for instantiating the GNSSFlowgraph and passing the required configuration. Once the flowgraph is defined an 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 the processing blocks via the thread-safe message queue.
Signal Processing Plane
Class hierarchy for the Signal Processing Plane
As shown in the above figure, gr::basic_block is the abstract base class for all signal processing blocks, a bare abstraction of an entity that has a name and a set of inputs and outputs. It is never instantiated directly; rather, this is the abstract parent class of both gr::hier_block2, which is a recursive container that adds or removes processing or hierarchical blocks to the internal graph, and gr::block, which is the abstract base class for all the processing blocks. A signal processing flow is constructed by creating a tree of hierarchical blocks, which at any level may also contain terminal nodes that actually implement signal processing functions.
Class gr::top_block is the top-level hierarchical block representing a flowgraph. It defines GNU Radio runtime functions used during the execution of the program: run(), start(), stop(), wait(), etc. As shown in the figure below, a subclass called GNSSBlockInterface is the common interface for all the GNSS-SDR modules. It defines pure virtual methods, that are required to be implemented by a derived class. Classes containing pure virtual methods are termed “abstract;” they cannot be instantiated directly, and a subclass of an abstract class can only be instantiated directly if all inherited pure virtual methods have been implemented by that class or a parent class.
General interface for signal processing blocks.
Subclassing GNSSBlockInterface, we defined interfaces for the receiver blocks. This hierarchy, shown below, provides the definition of different algorithms and different implementations, which will be instantiated according to the configuration. This strategy allows multiple implementations sharing a common interface, achieving the objective of decoupling interfaces from implementations: it defines a family of algorithms, encapsulates each one, and makes them interchangeable. Hence, we let the algorithm vary independently from the program that uses it.
Receiver’s class hierarchy
Brief description about the internal processes going on when the receiver is running.