Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 11 Current »

This folder contains a C++17 standard version of the IASDK. The SDK C++ API provides an object-based interface for controlling/configuring the radar and for processing incoming data.

This C++17 implementation additionally includes the source code of a peak finding function to operate on a single azimuth of raw radar data: the same functionality that is implemented within the radar firmware to enable Navigation Mode .

This version of the SDK was developed on Ubuntu 20.04. The Preferred compiler for building is Clang V10 but GCC 9.3.x should work.


Building the SDK

Prerequisites

sudo apt install build-essential clang g++ protobuf-compiler libprotobuf-dev cmake

Building using CMAKE

This assumes that the SDK has been cloned into ~/iasdk. To simplify the (arcane) CMake syntax, a simple batch script has been written to automate the build process. The script creates a folder, output, which holds the build. The script will create a subfolder - debug/release - depending on the option chosen at the commend line.

./bootstrap.sh [-v | --verbose] [ reset | clean | debug | release ]

Unless otherwise specified, the default build type isdebug.

The output executables are placed in thr directory bin, below the build-type folder. For example:

/iasdk/output/Debug/bin/colossus_client
/iasdk/output/Debug/bin/navigation_client
/iasdk/output/Debug/bin/connection_tester
/iasdk/output/Debug/bin/cat240_client
/iasdk/output/Debug/bin/nmea_server
/iasdk/output/Debug/bin/nmea_client

Building from VS Code

The project is configured to work with the Microsoft CMake Tools. If the CMake Tools are installed, opening the project in VSCode should detect the make files and configure the project accordingly. The first time a build is selected you will need to select a compiler kit. This will present a list of possible compiler configurations.

Open VS Code in the project folder

VS Code can be launched directly from wsl and it will pick up the project configurations directly.

(As previously, assume the SDK has been installed in ~/iasdk)

cd cpp/cpp_17
code .

Using the VS Code build task

The .vscode folder contains a build task configuration for invoking CMake. To build: * hit ctrl-shift-b * select Build

The following options are available:

Make        Compile the debug configuration of the project (all executables)
Clean       Clean the project (next build forces a re-compile of all files)
Configure   Re-run CMake on the project; no build
Build       Configure, then make the debug configuration (same as Configure + Make)


Example projects

The SDK comes with three example projects.

colossus_client

This project gives an example of how to create a radar client and connect up free-function handlers for processing various message types. In this case, the radar client requests, and receives, FFT data from the radar. The FFT data itself is not processed, but statistics about the data are presented - packet rate, packet size and packet time.

Note, when a client connects to a radar a configuration message is always sent. The radar client must process this message, before requesting any other types.

The colossus_client program has two command-line options, as follows:

-i - The IP address of the server   [default: 127.0.0.1] 
-p - The server port                [default: 6317]

If an option is not provided, its default value will be used.

navigation_client

The navigation_client project is a sample application that will peak search and report back upto ten targets per azimuth.

The class Peak_finder can be used to process FFT data and search for peaks. The algorithm will sub-resolve within radar bins and return a power at a distance in metres on the azmith being checked.

The algorithm implemented here will slide a window over the FFT data moving forwards by the size of the window, when the FFT has risen and then fallen, the peak resolving algorithm is run to sub-resolve the distance.

See Peak_finder.h for the data structure that is generated per azimuth

  • threshold - Threshold in dB

  • bins_to_operate_on - Radar bins window size to search for peaks in

  • start_bin - Start Bin

  • buffer_mode - Buffer mode should only be used with a staring radar

  • buffer_length - Buffer length

  • max_peaks_per_azimuth - Maximum number of peaks to find in a single azimuth

The navigation_client program has two command-line options, as follows:

-i - The IP address of the server   [default: 127.0.0.1] 
-p - The server port                [default: 6317] 

If an option is not provided, its default value will be used.

Connection_tester

The Connection tester project is a utility to stress-test the stability of the server. The program creates a radar client that randomly connects and disconnects from a server. On each connection, the radar client requests FFT data.

The Connection_tester object runs in its own thread-of-control and can therefore be run in parallel. Up to three Connection_tester objects can be run, all connecting/disconnecting to the same radar. More than three Connection_tester objects can be running, but a server will only allow a maximum of three connections at any one time.

The connection_tester program has three command-line options, as follows:

-i - The IP address of the server             [default: 127.0.0.1] 
-p - The server port                          [default: 6317] 
-c - Number of connections to run in parallel [default: 1] 

If an option is not provided, its default value will be used.

NOTE: The connection tester can be terminated with ctrl-c. This will send an asynchronous message to each Connection_tester object. The Connection_tester will only act upon this signal once it has completed its current connect/disconnect sequence. This means it may take several seconds for the program to end, and you may see additional connect/disconnects after the ctrl-c.

cat240_client

The cat240_client can connect to an ASTERIX CAT-240 source (for example, the cat240_server project) and will receive video messages. This project does not perform any significant processing on incoming video messages. NOTE: The cat240_client will not (currently) re-form video message packets that may have been split (by the server).

The cat240_server program has two command-line options:

-i - The IP address UDP packets are read from [default: 127.0.0.1] 
-p - The port UDP packets are read from       [default: 6317]

nmea_client

This project gives a simple example for receiving NMEA messages over a UDP connection. At present, only the following NMEA messages can be handled:

  • GPGGA

  • GPRMC

  • GPHDT

  • PASHR

The messaging classes are currently simplistic and only perform basic message processsing (validation, conversion to string/vector, etc.) The nmea_client program has two command-line options:

-i - The IP address UDP packets are read from [default: 127.0.0.1] 
-p - The port UDP packets are read from       [default: 6317]

nmea_server

This project gives a simple example for sending NMEA messages over a UDP connection. At present, only the following NMEA messages can be handled:

  • GPGGA

  • GPRMC

  • GPHDT

  • PASHR

The messaging classes allow construction of NMEA messages from strings or from vectors of std::uint8_t. There are currently no facilities for appending/removing message clauses. The nmea_server program has two command-line options:

-i - The IP address UDP packets sent to       [default: 127.0.0.1] 
-p - The port UDP packets are sent to         [default: 6317]

If the IP address supplied falls into the range of multicast addresses (224.0.0.1 - 239.255.255.255) the server will be configured to multicast its NMEA messages.

colossus_protocol_tester

The colossus_protocol_tester application tests the Colossus messaging protocol between the radar and a client. It verifies that the radar is responding to commands and emitting data correctly. The program runs through a sequence of test cases, and presents the results. Some tests may run for several seconds (for example, if messages are sent at periodic intervals).

The colossuse_protocol_tester program has three command-line options:

-i - The IP address UDP packets are read from       [default: 127.0.0.1] 
-p - The port UDP packets are read from             [default: 6317]
-t - The test case (specified as the message enum)  [ default: all]

For example:

$ ./colossus_protocol_tester -i 192.168.0.1 -t fft_data


Messaging protocol types

The SDK contains a minimum viable set of network messages for communicating to/from a radar. Currently the following protocols are supported: * Colossus, * Cambridge Pixel * ASTERIX CAT-240 * NMEA (limited capability).

All of the protocols follow a similar idiom for how they are sent, received and accessed. The protocols are designed to be extensible. Please see the /network/protocol/README.md file for a detailed overview of how the Colossus protocol code works, how it used, and how it can be extended.


Utilities

The SDK comes with a library of utilities classes and functions, designed to make programming easier. The utilities are used throughout the code. Notable examples include:

Time utilites (Time_utils.h)

The time utilities library provides an alternative to the C++ std::chrono library. The Time Utilities support both real-time and monotonic clocks. The main classes are: * Durations - A Duration represents a period of time. Durations have nanosecond resolution * Observations - An Observation represents a point in time; as a Duration from their clock's epoch.

Durations and Observations are designed to be compared manipulated in intuitive ways - for example, subtracting two Observations will yield the Duration between them. Durations and Observations also support expressive streaming options, meaning displaying/streaming time objects is simple.

Active classes (Active.h)

The Active class provides a high-level abstraction from OS threading and support for asynchronous messaging. See the Active.h header for more information on how to use the Active class.

Object lifetime managerment (pointer_types.h)

The code uses aliasing directives to make object lifetime management - ownership - as explicit as possible. Raw pointers for dynamically-allocated objects is discouraged. There is a distinct separation between object-to-object association (the "uses-a" relationship) and lifetime management of an object. owner_of and shared_owner types must never be used for association.

Under the hood: * owner_of is an alias of std::unique_ptr; * shared_owner is an alias of std::shared_ptr; * association_to wraps 'raw' pointers

Please see Utility/pointer_types.h for a more detailed explanation.

Networking utilities

The SDK contains a library of types for supporting networking. These include:

IP_address      A lightweight type for storing and manipulating IPv4 addresses
Port            A type alias for TCP port IDs
Endpoint        A simple structure for specifying IP address + port
net_conversion  A library of host- to network-endian conversions for common types


Programming with the SDK

Connection to a radar is handled through a Client object. A Client provides two basic interfaces:

  • Setting up callbacks for incoming Colossus messages

  • Sending Colossus messages (to the radar)

Constructing the Client

The Client must be constructed with the IP address and port number of the server:

#include "Colossus_client.h"
#include "IP_address.h"

using namespace Navtech::Networking;
using namespace Navtech::Networking::Colossus_protocol;
using namespace Navtech::Utility;

int main()
{
    Client radar_client { "198.168.0.1"_ipv4, "6317" };

    // more...
}

In the above code we are using the user-defined literal _ipv4 to construct an IP_address object from a string literal.

Creating a callback

Incoming messages are dispatched to an appropriate callback, which must be provided by the client. The callback can be any C++ Callable type - that is, a function, a member function or a lambda expression - that satisfies the function signature:

void (*callback)(Navtech::Networking::Colossus_protocol::Client&, Navtech::Networking::Colossus_protocol::Message&);

Note, the callback has two parameters, the (incoming) message, and a reference to the (calling) Client. This reference allows the callback to access the API of the Client without resorting to static (global) Client objects. An example of how this may be exploited is shown below.

In this simple example, we'll use a free function to process the incoming configuration.

#include "Colossus_client.h"
#include "IP_address.h"
#include "Colossus_protocol.h"

using namespace Navtech::Networking;
using namespace Navtech::Networking::Colossus_protocol;
using namespace Navtech::Utility;

// Define a callback to process incoming configuration messages
//
void process_config(Client& radar_client, Colossus_protocol::Message& msg)
{
    // See later...
}


int main()
{
    Client radar_client { "198.168.0.1"_ipv4, "6317" };

    // more...
}

We must register the handler with the Client and associate it with a particular message type.

#include "Colossus_client.h"
#include "IP_address.h"
#include "Colossus_protocol.h"

using namespace Navtech::Networking;
using namespace Navtech::Networking::Colossus_protocol;
using namespace Navtech::Utility;

void process_config(Client& radar_client, Colossus_protocol::Message& msg)
{
    // See later...
}


int main()
{
    Client radar_client { "198.168.0.1"_ipv4, "6317" };

    // Register the callback
    //
    radar_client.set_handler(Colossus_protocol::Type::configuration, process_config);

    // more...
}

Next, we must set the Client running, so that it can connect to the radar and begin processing messages. The Client runs in its own thread. Callbacks are executed in the context of the Client (more specifically, in the context of the Client's Dispatcher thread). It is your responsibility to protect against race conditions if your callback functions interact with other threads-of-control.

For this example, we will simply let the Client run for a period of time, before stopping.

#include "Colossus_client.h"
#include "IP_address.h"
#include "Colossus_protocol.h"

uusing namespace Navtech::Networking;
using namespace Navtech::Networking::Colossus_protocol;
using namespace Navtech::Utility;
using namespace Navtech::Time;
using namespace Navtech::Time::Monotonic;

void process_config(Client& radar_client, Colossus_protocol::Message& msg)
{
    // See later...
}


int main()
{
    Client radar_client { "198.168.0.1"_ipv4, "6317" };

    radar_client.set_handler(Colossus_protocol::Type::configuration, process_config);

    // Start the Client and let it run for a period of time.
    // Note the use of a user-defined literal for the sleep duration.
    //
    radar_client.start();
    sleep_for(30_sec);
    radar_client.stop();
}

Handling incoming messages

The Colossus_protocol::Message type encapsulates a buffer with a basic interface for accessing data. For more details on the Message type, please read /network/protocol/README.md, which explains the concept behind the Colossus messaging classes.

To access the radar data in a message, the simplest way (and our recommended way) is to perform a memory overlay onto the message data. The Message class provides an interface for this. Once the overlay has been done, the specific message's API can be accessed to read from the message buffer. The API has been constructed to hide any endianess issues, scaling or conversion that may be required to get from 'raw' message bytes to usable radar information.

#include "Colossus_client.h"
#include "IP_address.h"
#include "Colossus_protocol.h"
#include "Log.h"

using namespace Navtech::Networking;
using namespace Navtech::Networking::Colossus_protocol;
using namespace Navtech::Utility;
using namespace Navtech::Time;
using namespace Navtech::Time::Monotonic;

void process_config(Client& radar_client, Colossus_protocol::Message& msg)
{
    // Interpret the message data as a Configuration message
    //
    Colossus_protocol::Configuration* config = msg.view_as<Colossus_protocol::Configuration>();

    // Access the data via the pointer
    //
    stdout_log << "Azimuth samples [" << config->azimuth_samples() << "]" << endl;
    stdout_log << "Bin size        [" << config->bin_size()<< "]" << endl;
    stdout_log << "Range in bins   [" << config->range_in_bins()<< "]" << endl;
    stdout_log << "Encoder size    [" << config->encoder_size()<< "]" << endl;
    stdout_log << "Rotation rate   [" << config->rotation_speed()<< "]" << endl;
    stdout_log << "Range gain      [" << config->range_gain()<< "]" << endl;
    stdout_log << "Range offset    [" << config->range_offset()<< "]" << endl;

    // more...
}

In the example above we are using the logging utility, stdout_log. This utility extends C++ console output, whilst maintaining a similar interface (all the IO manipulators available to std::ostream are also available to stdout_log, for example).

Handling protocol buffer from a message

If the incoming message contains a protocol buffer, this data can be extracted into a protobuf object and then accessed through the normal protobuf API.

#include "Colossus_client.h"
#include "IP_address.h"
#include "Colossus_protocol.h"
#include "Log.h"
#include "configurationdata.pb.h"
#include "Protobuf_helpers.h"

// As previously...

void process_config(Client& radar_client, Colossus_protocol::Message& msg)
{
    using Navtech::Protobuf::from_vector_into;
    using namespace Colossus;
    using namespace Navtech::Networking;

    Colossus_protocol::Configuration* config = msg.view_as<Colossus_protocol::Configuration>();

    stdout_log << "Azimuth samples [" << config->azimuth_samples() << "]" << endl;
    stdout_log << "Bin size        [" << config->bin_size()<< "]" << endl;
    stdout_log << "Range in bins   [" << config->range_in_bins()<< "]" << endl;
    stdout_log << "Encoder size    [" << config->encoder_size()<< "]" << endl;
    stdout_log << "Rotation rate   [" << config->rotation_speed()<< "]" << endl;
    stdout_log << "Range gain      [" << config->range_gain()<< "]" << endl;
    stdout_log << "Range offset    [" << config->range_offset()<< "]" << endl;

    // Overlay onto the message memory, then extract the raw message payload
    // data into std::optional protobuf object using the helper function
    // Protobuf::from_vector_into.  If the conversion fails, the function
    // will return std::nullopt
    //
    std::optional<Protobuf::ConfigurationData> protobuf = from_vector_into<Protobuf::ConfigurationData>(config->to_vector());

    if (protobuf.has_value()) {
        stdout_log << "Radar ID   [" << protobuf->model().id()   << "]" << endl;
        stdout_log << "Radar name [" << protobuf->model().name() << "]" << endl;
        // etc...
    }
}

Sending a message

The full details of sending Colossus messages is beyond the scope of this 'getting started' overview. For full details of how to construct and send Colossus messages, please read /network/protocol/README.md.

In the case of a radar client, typically only simple messages are sent to the radar, to enable/disable features such as FFT data, or radar health.

Remember - if you enable data transmission from the radar you must have a handler for it, otherwise you will receive an error each time a message arrives!

void process_FFT(Client& radar_client, Colossus_protocol::Message& msg)
{
    // Process the incoming FFT data...
}


int main()
{
    Client radar_client { "192.168.0.1"_ipv4, "6317" };

    radar_client.set_handler(Colossus_protocol::Type::configuration, process_config);
    radar_client.set_handler(Colossus_protocol::Type::fft_data, process_FFT);

    radar_client.start();
    sleep_for(5_sec);

    // Construct a message to start FFT data and send it to the radar
    //
    Colossus_protocol::Message msg { };
    msg.type(Colossus_protocol::Type::start_fft_data);

    radar_client.send(std::move(msg));

    sleep_for(30_sec);
    radar_client.stop();
}

Sending a message within a callback

Commonly, you will want to send a message from within a callback. Since the callback has the Client as an argument, you can directly send a message.

Note, in the case of simple messages (with no header or payload), the send() method can construct the Colossus_protocol::Message object implicitly as part of the call.

void process_config(Client& radar_client, Colossus_protocol::Message& msg)
{
    using Navtech::Protobuf::from_vector_into;
    using namespace Colossus;
    using namespace Navtech::Networking;

    Colossus_protocol::Configuration* config = msg.view_as<Colossus_protocol::Configuration>();

    stdout_log << "Azimuth samples [" << config->azimuth_samples() << "]" << endl;
    stdout_log << "Bin size        [" << config->bin_size()<< "]" << endl;
    stdout_log << "Range in bins   [" << config->range_in_bins()<< "]" << endl;
    stdout_log << "Encoder size    [" << config->encoder_size()<< "]" << endl;
    stdout_log << "Rotation rate   [" << config->rotation_speed()<< "]" << endl;
    stdout_log << "Range gain      [" << config->range_gain()<< "]" << endl;
    stdout_log << "Range offset    [" << config->range_offset()<< "]" << endl;


   auto protobuf = from_vector_into<Protobuf::ConfigurationData>(config->to_vector());

    if (protobuf.has_value()) {
        stdout_log << "Radar ID   [" << protobuf->model().id()   << "]" << endl;
        stdout_log << "Radar name [" << protobuf->model().name() << "]" << endl;
    }

    // Send a simple message to the radar
    //
    radar_client.send(Colossus_protocol::Type::start_fft_data);
}

  • No labels