Integrating Cloud Solutions with Qt

Welcome to the final installment of our Qt for Automation blog mini-series. If you missed the previous articles, please have a look at Lars' blog post, where you can find an overview of all the topics (remote UIs, optimizing device communication, and more) we've covered.

These days, using the cloud for predictive maintenance, analytics or feature updates is a de facto standard in the automation space. Basically, any newly designed product has some server communication at its core.

However, the majority of solutions in the field were designed and productized when communication technology was not at today’s level. Still, attempts are being made to attach connectivity to such solutions. The mission statement is to “cloudify” an existing solution, which uses some internal protocol or infrastructure.

Let’s consider our favorite example in this series, the Sensor Tag demo. Multiple sensors publish their telemetry data to an MQTT broker. If this infrastructure cannot be changed, a cloud-based solution needs to be attached to it, like in the graphic below:

cloud_connector

The first thing we need is a connector, which translates internal protocols and payload-formats into what a cloud solution accepts as valid input.

So, let’s create one!

One of the great features in Qt is that you can connect to any cloud solution provider. Whichever you chose, the principles in this post will be the same. We are going to use Microsoft Azure and its IoTHub solution in our example.

The demo contains a topic called “sensors/active”. Each active device continuously reports itself as available on this topic. If a connection is interrupted or the device disconnects, a retained message is sent indicating the offline state. This is going to be the entry point for the connector.

Subscribing to that topic is done with QMqttClient::subscribe

 

m_activeSub = m_client->subscribe(QLatin1String("sensors/active"), 1);


Each time a new client goes online, a new SensorInformation instance is created. The sensor is stored in conjunction with its ID in a map.

[...]
    if (split.at(1) == "Online" && !m_sensors.contains(split.at(0))) {
        const QString subName = QString::fromLocal8Bit("sensors/%1/#").arg(QString::fromUtf8(split.at(0)));
        auto sub = m_client->subscribe(subName, 1);
        if (!sub)
            return;

auto sensor = new SensorInformation(sub, this); connect(sensor, &SensorInformation::publishRequested, m_azure, &AzureConnection::publishMessage); m_sensors.insert(split.at(0), sensor); [...]

Additionally, the SensorInformation stores the subscription and parses the received messages. Periodically (or whenever a single value changes) the device state requests a sync to the cloud via the publishRequested signal.

The IoTHub expects messages to have its payload formatted as JSON. As demonstrated in our previous posts of this series, this is a simple task using Qt, and more specifically, QJsonDocument.

To send messages to Azure, a thin layer around the IoTHub SDK is created to hook into Qt. A minimalistic approach looks like this:

class AzureConnection : public QObject
{
    Q_OBJECT
public:
    AzureConnection();
    ~AzureConnection();

void init(const QString &connectionString); void cleanup();

Q_SIGNALS: void messageReceived(const QByteArray &content); void messageSent(); void messageError();

public Q_SLOTS: void publishMessage(const QByteArray &content);

private: AzureConnectionThread *m_thread; };

Azure expects each connection to have a unique ID to identify the account, the device and the session. In this example, we expect this string to be known beforehand. An API exists to do device provisioning to Azure, but that is not part of this article.

The initialization is handled via

    if (platform_init() != 0)
        return false;

m_iotClientHandle = IoTHubClient_LL_CreateFromConnectionString(qPrintable(connectionString), AMQP_Protocol); if (m_iotClientHandle == NULL) return false;

IOTHUB_CLIENT_RESULT receiveEvent = IoTHubClient_LL_SetMessageCallback(m_iotClientHandle, ReceiveMessageCallback, this); if (receiveEvent != IOTHUB_CLIENT_OK) return false;

return true;

The SDK itself handles everything via callbacks, more specifically the message state. Once a message is sent by calling IoTHubClient_LL_SendEventAsync() the state of the message is handled in a callback. The various options are handled in SendConfirmationCallback()

    EVENT_INSTANCE *eventInstance = (EVENT_INSTANCE *)userContextCallback;
    switch (result) {
    case IOTHUB_CLIENT_CONFIRMATION_OK:
        qDebug() << "ID: " << eventInstance->messageTrackingId << " confirmed";
        break;
    case IOTHUB_CLIENT_CONFIRMATION_BECAUSE_DESTROY:
        qDebug() << "ID: " << eventInstance->messageTrackingId << " not confirmed due to destroy";
        break;
    case IOTHUB_CLIENT_CONFIRMATION_MESSAGE_TIMEOUT:
        qDebug() << "ID: " << eventInstance->messageTrackingId << " not confirmed due to timeout";
        break;
    case IOTHUB_CLIENT_CONFIRMATION_ERROR:
        qDebug() << "ID: " << eventInstance->messageTrackingId << " not confirmed due to confirmation error";
        break;
    default:
        qDebug() << "ID: " << eventInstance->messageTrackingId << " Unknown confirmation";
        break;
    }

Once a message has returned the confirmation state, it has been parsed and processed by the IoTHub. Any previous state indicates that it has not yet been stored and is cached locally.

After the messages have been confirmed, analytics (or any other operation) can be applied to the data (eg. TimeSeries Insights or the new IoTCentral products). Check out the custom dashboard in our demo video at the Embedded World 2018:

[embed]https://www.youtube.com/watch?v=k-wrIixUcKQ[/embed]

This example uses one executable to handle all available sensors. Naturally, a highly scalable setup demands further requirements. Given that the presented Qt connector runs on a server instance already, it could easily be integrated into a virtualization scenario using containers or similar.

As you have seen, not only can you use Qt to create devices and gateways with and without an HMI, but also bridges between various aspects of a deployment setup. With a consistent API, all building blocks can easily be moved, while the code stays the same due to its cross-platform capabilities.


Blog Topics:

Comments