Quick CoAP Multicast Discovery Example▲

Image non disponible

The Quick CoAP Multicast Discovery Example demonstrates how to register QCoapClient as a QML type and use it in a Qt Quick application for CoAP multicast resource discovery.

Qt CoAP does not provide a QML API in its current version. However, you can make the C++ classes of the module available to QML as shown in this example.

Running the Example▲

To run the example application, you first need to set up and start at least one CoAP server supporting multicast resource discovery. You have the following options:

  • Manually build and run CoAP servers using libcoap, Californium, or any other CoAP server implementation, which supports multicast and resource discovery features.

  • Use the ready Docker image available at Docker Hub, which builds and starts CoAP server based on Californium's multicast server example.

Using the Docker-based Test Server▲

The following command pulls the docker container for the CoAP server from the Docker Hub and starts it:

 
Sélectionnez
docker run --name coap-multicast-server -d --rm --net=host sokurazy/coap-multicast-test-server:californium.2.0.x

You can run more than one multicast CoAP servers (on the same host or other hosts in the network) by passing a different --name to the command above.

Creating a Client and Using It with QML▲

We create the QmlCoapMulticastClient class with the QCoapClient class as a base class:

 
Sélectionnez
class QmlCoapMulticastClient : public QCoapClient
{
    Q_OBJECT

public:
    QmlCoapMulticastClient(QObject *parent = nullptr);

    Q_INVOKABLE void discover(const QString &host, int port, const QString &discoveryPath);
    Q_INVOKABLE void discover(QtCoap::MulticastGroup group, int port, const QString &discoveryPath);

Q_SIGNALS:
    void discovered(const QmlCoapResource &resource);
    void finished(int error);

public slots:
    void onDiscovered(QCoapResourceDiscoveryReply *reply, const QList<QCoapResource> &resources);
};

In the main.cpp file, we register the QmlCoapMulticastClient class as a QML type:

 
Sélectionnez
    qmlRegisterType<QmlCoapMulticastClient>("CoapMulticastClient", 1, 0, "CoapMulticastClient");

We also register the QtCoap namespace, to be able to use it in QML code:

 
Sélectionnez
    qmlRegisterUncreatableMetaObject(QtCoap::staticMetaObject, "qtcoap.example.namespace", 1, 0,
                                     "QtCoap", "Access to enums is read-only");

Now in the QML code, we can import and use these types:

 
Sélectionnez
    ...
import CoapMulticastClient
import qtcoap.example.namespace
    ...
    CoapMulticastClient {
        id: client
        onDiscovered: addResource(resource)

        onFinished: {
            statusLabel.text = (error === QtCoap.Error.Ok)
                    ? qsTr("Finished resource discovery.")
                    : qsTr("Resource discovery failed with error code: %1").arg(error)
        }

        onError:
            statusLabel.text = qsTr("Resource discovery failed with error code: %1").arg(error)
    }
    ...

The QCoapClient::error() signal triggers the onError signal handler of CoapMulticastClient, and the QmlCoapMulticastClient::finished() signal triggers the onFinished signal handler, to show the request's status in the UI. Note that we are not using the QCoapClient::finished() signal directly, because it takes a QCoapReply as a parameter (which is not a QML type), and we are interested only in the error code.

In the QmlCoapMulticastClient's constructor, we arrange for the QCoapClient::finished() signal to be forwarded to the QmlCoapMulticastClient::finished() signal:

 
Sélectionnez
QmlCoapMulticastClient::QmlCoapMulticastClient(QObject *parent)
    : QCoapClient(QtCoap::SecurityMode::NoSecurity, parent)
{
    connect(this, &QCoapClient::finished, this,
            [this](QCoapReply *reply) {
                    if (reply)
                        emit finished(static_cast<int>(reply->errorReceived()));
                    else
                        qCWarning(lcCoapClient, "Something went wrong, received a null reply");
            });
}

When the Discover button is pressed, we invoke one of the overloaded discover() methods, based on the selected multicast group:

 
Sélectionnez
    ...
        Button {
            id: discoverButton
            text: qsTr("Discover")
            Layout.columnSpan: 2

            onClicked: {
                var currentGroup = groupComboBox.model.get(groupComboBox.currentIndex).value;

                var path = "";
                if (currentGroup !== - 1) {
                    client.discover(currentGroup, parseInt(portField.text),
                                    discoveryPathField.text);
                    path = groupComboBox.currentText;
                } else {
                    client.discover(customGroupField.text, parseInt(portField.text),
                                    discoveryPathField.text);
                    path = customGroupField.text + discoveryPathField.text;
                }
                statusLabel.text = qsTr("Discovering resources at %1...").arg(path);
            }
        }
    ...

This overload is called when a custom multicast group or a host address is selected:

 
Sélectionnez
void QmlCoapMulticastClient::discover(const QString &host, int port, const QString &discoveryPath)
{
    QUrl url;
    url.setHost(host);
    url.setPort(port);

    QCoapResourceDiscoveryReply *discoverReply = QCoapClient::discover(url, discoveryPath);
    if (discoverReply) {
        connect(discoverReply, &QCoapResourceDiscoveryReply::discovered,
                this, &QmlCoapMulticastClient::onDiscovered);
    } else {
        qCWarning(lcCoapClient, "Discovery request failed.");
    }
}

And this overload is called when one of the suggested multicast groups is selected in the UI: