Engage And Qt - rallytac/pub GitHub Wiki

Using Engage With Qt

If you're familiar with the Qt framework you know that developing GUI and non-GUI cross-platform C++ (and other language) applications is a walk in the park. In this article we're going to explore building a super-teeny, minimal GUI application that incorporates the Engage Engine to provides a simple Push-To-Talk user interface.

WARNING: Any C++ programmer worth their salt is going to look at this code and feel their OCD rising. If this is you and you feel your health and well-being may be impacted, please stop reading this article right now!

What We'll Build

This is it - a simple little window with one button on it that, when pressed, will transmit to a group and, when released, ends transmission.

image

Note that for our demo purposes ...

  • ... we're not going to do any error-checking.
  • ... we're not going to worry about multi-threading, race conditions, Engage event hooks, or anything else. We just want something super-simple to demonstrate using Engage from a Qt GUI application.

What You'll Need

  • Qt loves C++ and so does Engage. You're going to need your favorite version of Qt (we recommend the latest version), including Qr Creator and any other goodies needed for C++ UI development for the platforms you want to target.
  • You'll obviously need the Engage Engine library for target platform(s) as well as the EngageInterface.h header file. Best is to get this by cloning our public Github repository.

Engage needs a few configuration items here. This includes a certificate store, and JSON configurations for the Engine policy, user identity, and group configuration.

Certificate Store

In the policy JSON below you'll see references to a certificate and private key. You're going to need a certificate store that contains these elements. You can find a suitable certificate store in the certificates directory of the public repository.

Policy JSON

The tiniest policy that we need for Engage to be happy is one that specifies X.509 certificate information. Without this basic piece of configuration, the Engine will simply abort (we're pretty serious about security around here). So, our super-minimal policy is as follows:

qt_engine_policy.json

{
    "security":{
        "certificate":{
            "certificate":"@certstore://rtsFactoryDefaultEngage",   // This comes from the certificate store
            "key":"@certstore://rtsFactoryDefaultEngage"            // This also comes from the certificate store
        }
    }
}

Identity JSON

When we transmit (and also for purposes of system presence) we're going to want to identify our user. We'll need a little bit of JSON for this.

qt_identity.json

{
    "userId":"[email protected]",
    "displayName":"Qt Test User",
    "alias":"QTUSER"
}

Group Configuration JSON

The real meat here is the JSON that describes the group/channel we'll be using in our demo. We're going to define a group named Qt Alpha that runs solely on IP multicast, is encrypted, and uses the Opus codec. Here's the configuration..When we transmit (and also for purposes of system presence) we're going to want to identify our user. We'll need a little bit of JSON for this.

qt_alpha.json

{
    "type":1,                                           // This is an audio group
    "id":"{91d1471f-c3b1-459f-8c5f-d84888865dd4}",      // Remember this identifier - you'll see it in the C++ code
    "name":"Qt Alpha",                      
    "cryptoPassword":"AABBCCAABBCCAABBCCAABBCCAABBCCAABBCCAABBCCAABBCCAABBCCAABBCCAABB",
    "rx":{
        "address":"239.42.42.77",
        "port":23000
    },
    "tx":{
        "address":"239.42.42.77",
        "port":23000
    },
    "txAudio":{
        "encoder":25                                    // We'll use Opus at 16kbps
    }
}

Let's Go

Put The UI Together

1. Create a new Qt Widgets application, give it a name, and accept other defaults - making sure you select a desktop build.

2. Update the Qt project file

Next, open the project file (with the .pro extension) and tell it where to find Engage headers and the Engage library. In this example we're building on Mac OSX so we'll tell Qt Creator that our library is named engage-shared and it can be found in /Global/github/pub/bin/latest/darwin_x64/. (It's best to add this stuff at the end of the project file by the way.)

# Engage-y stuff
macx: LIBS += -L/Global/github/pub/bin/latest/darwin_x64/ -lengage-shared

INCLUDEPATH += /Global/github/pub/api/c/include
DEPENDPATH += /Global/github/pub/api/c/include

3. Add EngageInterface.h

Open mainwindow.cpp and add the following:

#include "EngageInterface.h"

4. Once you're through all this, open the UI designer for the main window and drag a PushButton control onto the layout. Name it pttButton and give it a text attribute of PTT.

image

5. Connect the button's pressed slot. This will create a method in the main window's class named on_pttButton_pressed and take you to it. Leave it alone for the moment and return to the UI designer.

image image

6. Connect the button's released slot. This will create a method in the main window's class named on_pttButton_released and take you to it.

7. Now that you have your two handlers in place in the main window's class, you can do something useful to make sure the code is handling mouse events. Update the code to change the text on the button to "Transmitting" when the button is pressed and back to "PTT" when it's released.

void MainWindow::on_pttButton_pressed()
{
    ui->pttButton->setText("Transmitting");
}

void MainWindow::on_pttButton_released()
{
    ui->pttButton->setText("PTT");
}

(You should build and run the app at this point just to make sure that everything is moving along smoothly.)

Get Engaged

Alrighty, we've handled all the Qt UI stuff we want to do. Let's get Engage involved.

The basic plan here is to get Engage initialized and started as soon as the window comes up on the screen. Right away, we'll also create our group (remember it was named Qt Alpha and had an ID of {91d1471f-c3b1-459f-8c5f-d84888865dd4}), and tell Engage to join it (meaning we want Engage to get the group connected to the network and get ready for transmit and receive). We're going to do all that right inside the constructor for the window's C++ class. When our window closes, we'll shut things down in the destructor.

To transmit, we're going to change on_pttButton_pressed and on_pttButton_released to tell Engage to begin and end transmit respectively.

Let's get to it. Update the source code for mainwindow.cpp to the following, build, and run.

IMPORTANT: Make sure that the certificate store and JSON files referenced in the codeare valid paths for your machine.

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include "EngageInterface.h"

static const char *CERT_STORE_FILE      = "/Global/github/work/all-rts-certs.certstore";
static const char *CERT_STORE_PWD       = "";
static const char *POLICY_FILE          = "/Global/github/work/qt_engine_policy.json";
static const char *IDENTITY_FILE        = "/Global/github/work/qt_identity.json";
static const char *GROUP_FILE           = "/Global/github/work/qt_alpha.json";
static const char *GROUP_ID             = "{91d1471f-c3b1-459f-8c5f-d84888865dd4}";

static std::string readTextFile(const char *pszFn)
{
    FILE *fp;
    std::string rc;

    // We need to open the file a little differently on Windows
    #ifndef WIN32
        fp = fopen(pszFn, "rb");
    #else
        if(fopen_s(&fp, pszFn, "rb") != 0)
        {
            fp = nullptr;
        }
    #endif

    if(fp != nullptr)
    {
        fseek(fp, 0, SEEK_END);
        size_t sz = ftell(fp);
        fseek(fp, 0, SEEK_SET);
        char *buff = new char[sz + 1];
        if( fread(buff, 1, sz, fp) == sz )
        {
            buff[sz] = 0;
            rc = buff;
        }
        delete[] buff;

        fclose(fp);
    }

    return rc;
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // This should give any C++ programmer the hives - doing all this stuff
    // in the class constructor.  But we'll tolerate this for this example.

    // Load our policy, identity, and group JSON from files.
    std::string policyJson = readTextFile(POLICY_FILE);
    std::string identityJson = readTextFile(IDENTITY_FILE);
    std::string groupJson = readTextFile(GROUP_FILE);

    // Tell Engage to use this certificate store.
    engageOpenCertStore(CERT_STORE_FILE, CERT_STORE_PWD);

    // Initialize the Engage Engine with the policy and identity.
    engageInitialize(policyJson.c_str(), identityJson.c_str(), "");

    // Start it up.
    engageStart();

    // Create our single group.
    engageCreateGroup(groupJson.c_str());

    // ... and join it.
    engageJoinGroup(GROUP_ID);
}

MainWindow::~MainWindow()
{
    // Just like with the constructor, we'll do a horrible thing and do
    // a whole lot of work in the destructor.

    // Stop Engage.
    engageStop();

    // Shut everything down.
    engageShutdown();

    delete ui;
}

void MainWindow::on_pttButton_pressed()
{
    // Change the button text to indicate we're transmitting.
    ui->pttButton->setText("Transmitting");

    // Begin transmitting on our group.
    engageBeginGroupTx(GROUP_ID, 0, 0);
}

void MainWindow::on_pttButton_released()
{
    // Change the button text to indicate we're NOT transmitting.
    ui->pttButton->setText("PTT");

    // End transmission.
    engageEndGroupTx(GROUP_ID);
}

That's It

OK, you're done! Not so bad huh!?

Of course, there's a LOT more stuff you can (and should) be doing such as error-handling, responding to Engage-generated events, and so on. But the barebones example presented here should give you an idea of how to wire up a cross-platform C++ UI application to Engage.

We hope you enjoyed playing along.