Qt programming for HTTP REST clients

1
36618

This article discusses Qt support for connectivity to IoT platforms like ThingSpeak using HTTP REST APIs. It also focuses on handling JSON data in terms of encoding, parsing, URL encoding and forming query strings.

Qt is a cross-platform development framework designed to provide eye candy GUI features and to give rich API support for Web communication, graph plotting, data exchange, 3D visualisation, multimedia handling, location tracking, sensor interfacing, etc. Qt applications can be built for various desktop platforms like Linux, Mac OS and Windows; for embedded platforms like Raspberry Pi and QNX, and also for mobile platforms like Android, iOS and Windows Mobile.

This article explores Qt support for connectivity to IoT platforms like ThingSpeak using HTTP REST APIs, and also focuses on handling JSON data in terms of encoding, parsing, URL encoding, forming query strings, etc. The assumption is that readers have basic familiarity with Qt and RESTful operations using HTTP support. If you are new to the Qt environment, do refer to some of the previous articles published in earlier editions of OSFY, as listed in the References, before proceeding.

About HTTP REST and ThingSpeak

ThingSpeak is an open source platform for building IoT applications, which supports connectivity using HTTP REST APIs. One can send data using the POST method with a request payload or the GET method with a query string, and retrieve data in JSON or XML formats using the GET method. Recently, support for sending data using MQTT Publish has also been added. ThingSpeak has good integration support for MATLAB from Mathworks, which helps it to offer better analysis and visualisation of data. It provides SDKs in various languages for connectivity or one can try to connect using any HTTP client of the preferred language.

In ThingSpeak, data is stored in terms of channels, and each channel is associated with an ID, name, description and various applicable fields along with some tags, geo-location, etc. For simple and better authentication, API keys are provided for read/write operations and a channel key for meta operations. The API keys of a specific channel can be embedded in a query string or post data for authentication. Secure HTTP (https) is preferred to prevent unauthorised access of API keys by eavesdroppers. A channel can also be made public during the creation or by changing the settings later, in which case no read key is required for viewing the data.

One can log in to ThingSpeak using a Mathworks account, which is free of cost, and create any number of channels and send maximum feeds—the only limitation on updating channels is an interval of 15 seconds. Once the channel is created with a suitable description, applicable fields and meta information, you can identify the read or write API keys under Channel settings and the key for channel meta operations in the Profile section.

Let’s now look at how a REST client for connecting with ThingSpeak can be built.

Qt HTTP connectivity

In order to talk to any network server using the HTTP protocol, Qt provides the QNetworkAccessManager class, which has asynchronous methods like get, post, put and deleteResource to perform various REST operations. These methods take the QNetworkRequest object as an argument, as well as an additional argument for request payload in the form of QByteArray for POST, PUT operations. QNetworkRequest mainly contains the URL in the form of a QUrl object, which encapsulates the protocol, host name, port, path, query string, etc. The QUrlQuery class can be used to form a QueryString effectively. These methods return the response in the form of a QNetworkReply, which needs to be explicitly destroyed using the deleteLater method in the slot connected to the finished signal of QNetworkAccessManager.

Let’s consider a channel with three fields representing temperature, humidity and pressure. Let us assume that the various API keys and channel numbers are initialised as follows:

QString RDKey = “XXXXXXXXXXXXXXXX”;
QString WRKey = “XXXXXXXXXXXXXXXX”;
QString CHKey = “XXXXXXXXXXXXXXXX”;
Qstring CHNum = “xxxxxx”;
Figure 1: Sending a feed using POST

To send a feed, we need to perform the POST method with the steps that follow, using the URL https://api.thingspeak.com/update.json and the following JSON data as request payload:

{
“api_key”:”XXXXXXXX XXXXXXXX”,
“field1”:”25”,
“field2”:”72”,
“field3”:”900”
}

Step 1: Let’s prepare the data for POST in JSON format, assuming that tval, hval and pval represent the temperature, humidity and pressure values in integer form.

QVariantMap feed;
feed.insert(“api_key”,WRKey);
feed.insert(“field1”,QVariant(tval).toString());
feed.insert(“field2”,QVariant(hval).toString());
feed.insert(“field3”,Qvariant(pval).toString());
QByteArray payload=QJsonDocument::fromVariant(feed).toJson();

You may verify the prepared JSON data in QString format as follows:

qDebug() << Qvariant(payload).toString();

Or you can display it in a QLineEdit or QTextEdit widget for testing purposes.

Step 2: Prepare the URL as follows:

QUrl myurl;
myurl.setScheme(“http”); //https also applicable
myurl.setHost(“api.thingspeak.com”); 
myurl.setPath(“/update.json”);

We are skipping the user name and password as we are using API keys for authentication. We are also skipping setPort as the service is running on the default port 80. You can verify the prepared URL as follows:

qDebug() << myurl.toString();

Step 3: Prepare the network request, as follows:

QNetworkRequest request;
request.setUrl(myurl);
request.setHeader(QNetworkRequest::ContentTypeHeader,
”application/json”);
Figure 2: Sending a feed using GET

Step 4: Perform the POST operation using the following code in the slot connected to the Publish PushButton click.

QNetworkAccessManager *restclient; //in class
restclient = new QNetworkAccessManager(this); //constructor
QNetworkReply *reply = restclient->post(request,payload);
qDebug() << reply->readAll();

Restclient can be declared in the MainWindow or the Dialog class and the QNetworkAccessManager object can be created in constructor, as one object is sufficient to perform all the operations.

The same update operation can be achieved using the GET method and data encoded in the URL as a query string:

QUrlQuery querystr; 
querystr.addQueryItem(“api_key”,WRKey);
querystr.addQueryItem(“field1”,”25”);
querystr.addQueryItem(“field2”,”72”);
querystr.addQueryItem(“field3”,”900”); 
myurl.setScheme(“https”);
myurl.setHost(“api.thingspeak.com”);
myurl.setPath(“/update”);
myurl.setQuery(querystr);
request.setUrl(myurl);
reply = restclient->get(myurl);
qDebug() << reply->readAll();

Alternatively, data encoded in the URL query string format can be used to perform the POST operation using the same URL api.thingspeak.com/update.json:

//Prepare querystr similar to above steps
request.setHeader(QNetworkRequest::ContentTypeHeader, “application/x-www-form-urlencoded”);
QByteArray postdata = Qvariant(querystr).toByteArray();
restclient->post(myurl,postdata);

Retrieving data from ThingSpeak

To retrieve the feeds from ThingSpeak, we need to perform the GET operation on http://api.thingspeak.com/channels/xxxxxx/feeds.json and api_key=XXX..XX as the query string. For this purpose, use the following code in the slot connected to the Retrieve PushButton click. You can append results=10 to the query string to limit the number of results to the last 10 feeds.

QUrl url;
url.setScheme(“http”);
url.setHost(“api.thingspeak.com”);
url.setPath(“/channels/”+CHNum+”/feeds.json”);
url.setQuery(“api_key=”+RDKey+”&results=10”);
request.setUrl(url);
reply=nwManager->get(request);

To process the responses holding all feeds in the JSON format, connect the finished signal of restclient to a suitable custom slot.

QObject::connect(restclient, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply *)));

Parsing JSON data

The returned JSON data has two primary fields with the names channel and feeds, and the value of feeds is an array of all feeds, where each array element consists of fields like entry_id, field1, field2, field3, created_at, etc.

To traverse all array elements and retrieve the specific fields in each, you can use the following code to parse the JSON data in the replyFinished slot:

QJsonDocument jsdoc;
jsdoc = QJsonDocument::fromJson(reply->readAll());
QJsonObject jsobj = jsdoc.object();
QJsonArray jsarr = jsobj[“feeds”].toArray();
foreach (const QJsonValue &value, jsarr) {
QJsonObject jsob = value.toObject();
qDebug() << jsob[“entry_id”].toInt();
qDebug() << jsob[“field1”].toString();
qDebug() << jsob[“field2”].toString();
qDebug() << jsob[“field3”].toString();
qDebug() << jsob[“created_at”].toString();
}
reply->deleteLater();
Figure 3: Retreiving data and displaying it in tabular form

Rendering data in tabular form

To display the data in tabular form, create a TableWidget through TableView with the name tableFeeds and add the following code in the slot connected to the Retrieve PushButton click.

ui->tableFeeds->clearContents();
ui->tableFeeds->setColumnCount(5);
ui->tableFeeds->setRowCount(jsarr.count());
foreach (const QJsonValue &value, jsarr) {
QJsonObject jsob = value.toObject();
ui->tableFeeds->setItem(k,0,
new QTableWidgetItem(jsob[“entry_id”].toString()));
ui->tableFeeds->setItem(k,1, new QTableWidgetItem(jsob[“field1”].toString()));
ui->tableFeeds->setItem(k,2, new QTableWidgetItem(jsob[“field2”].toString()));
ui->tableFeeds->setItem(k,3, new QTableWidgetItem(jsob[“field3”].toString()));
ui->tableFeeds->setItem(k,4, new QTableWidgetItem(jsob[“created_at”].toString()));
k++;
}

You can also plot the graph with the above feeds using the third party library, QCustomPlot or with the help of Qt Charts introduced in version 5.7.0.

As an example, we have discussed connectivity to ThingSpeak in this article. You can apply these concepts to other IoT platforms and database servers like InfluxDb, which support HTTP REST based connectivity.

1 COMMENT

LEAVE A REPLY

Please enter your comment!
Please enter your name here