Schnelle CoAP-Multicast-Ermittlung

Verwendung des CoAP-Clients für eine Multicast-Ressourcenermittlung mit einer Qt Quick Benutzeroberfläche.

Das Beispiel für die schnelle CoAP-Multicast-Ermittlung zeigt, wie QCoapClient als QML-Typ registriert und in einer Qt Quick -Anwendung für die CoAP-Multicast-Ressourcenermittlung verwendet werden kann.

Hinweis: Qt CoAP bietet in seiner aktuellen Version keine QML-API. Sie können jedoch die C++-Klassen des Moduls für QML verfügbar machen, wie in diesem Beispiel gezeigt.

Ausführen des Beispiels

Zum Ausführen des Beispiels von Qt Creatorzu starten, öffnen Sie den Modus Welcome und wählen Sie das Beispiel aus Examples. Weitere Informationen finden Sie unter Erstellen und Ausführen eines Beispiels.

Einrichten eines CoAP-Servers

Um die Beispielanwendung auszuführen, müssen Sie zunächst mindestens einen CoAP-Server einrichten und starten, der die Multicast-Ressourcenerkennung unterstützt. Sie haben die folgenden Möglichkeiten:

  • Manuelles Erstellen und Ausführen von CoAP-Servern mit libcoap, Californium oder einer anderen CoAP-Server-Implementierung, die Multicast- und Ressourcenerkennungsfunktionen unterstützt.
  • Verwenden Sie das fertige Docker-Image, das auf Docker Hub verfügbar ist und CoAP-Server auf der Grundlage des Multicast-Serverbeispiels von Californium erstellt und startet.
Verwendung des Docker-basierten Test-Servers

Der folgende Befehl zieht den Docker-Container für den CoAP-Server aus dem Docker Hub und startet ihn:

docker run --name coap-multicast-server -d --rm --net=host tqtc/coap-multicast-test-server:californium-2.0.0

Hinweis: Sie können mehr als einen Multicast-CoAP-Server betreiben (auf demselben Host oder anderen Hosts im Netzwerk), indem Sie dem obigen Befehl ein anderes --name übergeben.

Um den Docker-Container nach der Verwendung zu beenden, erhalten Sie zunächst die ID des Containers, indem Sie den Befehl docker ps ausführen. Die Ausgabe sieht dann wie folgt aus:

$ docker ps
CONTAINER ID   IMAGE
8b991fae7789   tqtc/coap-multicast-test-server:californium-2.0.0

Danach verwenden Sie diese ID, um den Container zu beenden:

docker stop <container_id>

C++-Klassen für QML freilegen

In diesem Beispiel müssen Sie die Klassen QCoapResource und QCoapClient sowie den Namespace QtCoap für QML freigeben. Um dies zu erreichen, erstellen Sie benutzerdefinierte Wrapper-Klassen und verwenden die speziellen Registrierungsmakros.

Erstellen Sie die Klasse QmlCoapResource als Wrapper um QCoapResource. Verwenden Sie das Makro Q_PROPERTY, um mehrere Eigenschaften von QML aus zugänglich zu machen. Die Klasse muss nicht direkt von QML aus instanzierbar sein, verwenden Sie also das QML_ANONYMOUS Makro, um sie zu registrieren.

class QmlCoapResource : public QCoapResource
{
    Q_GADGET
    Q_PROPERTY(QString title READ title)
    Q_PROPERTY(QString host READ hostStr)
    Q_PROPERTY(QString path READ path)

    QML_ANONYMOUS
public:
    QmlCoapResource() : QCoapResource() {}
    QmlCoapResource(const QCoapResource &resource)
        : QCoapResource(resource) {}

    QString hostStr() const { return host().toString(); }
};

Danach erstellen Sie die Klasse QmlCoapMulticastClient mit der Klasse QCoapClient als Basisklasse. Verwenden Sie das Makro Q_PROPERTY, um eine benutzerdefinierte Eigenschaft freizugeben, und erstellen Sie außerdem mehrere Methoden Q_INVOKABLE. Sowohl auf die Eigenschaft als auch auf die aufrufbaren Methoden kann von QML aus zugegriffen werden. Im Gegensatz zu QmlCoapResource möchten Sie diese Klasse von QML aus erstellen können, daher verwenden Sie das Makro QML_NAMED_ELEMENT, um die Klasse in QML zu registrieren.

class QmlCoapMulticastClient : public QCoapClient
{
    Q_OBJECT

    Q_PROPERTY(bool isDiscovering READ isDiscovering NOTIFY isDiscoveringChanged)

    QML_NAMED_ELEMENT(CoapMulticastClient)
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_INVOKABLE void stopDiscovery();

    bool isDiscovering() const;

Q_SIGNALS:
    void discovered(const QmlCoapResource &resource);
    void finished(int error);
    // The bool parameter is not provided, because the signal is only used by
    // the QML property system, and it does not use the passed value anyway.
    void isDiscoveringChanged();

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

private:
    QCoapResourceDiscoveryReply *m_reply = nullptr;
};

Schließlich registrieren Sie den Namespace QtCoap, damit Sie die dort bereitgestellten Enums verwenden können:

namespace QCoapForeignNamespace
{
    Q_NAMESPACE
    QML_FOREIGN_NAMESPACE(QtCoap)
    QML_NAMED_ELEMENT(QtCoap)
}

Anpassen der Build-Dateien

Um die benutzerdefinierten Typen in QML verfügbar zu machen, aktualisieren Sie die Build-Systemdateien entsprechend.

CMake-Build

Für einen CMake-basierten Build, fügen Sie folgendes zur CMakeLists.txt hinzu:

qt_add_qml_module(quickmulticastclient
    URI CoapClientModule
    VERSION 1.0
    SOURCES
        qmlcoapmulticastclient.cpp qmlcoapmulticastclient.h
    QML_FILES
        Main.qml
)
qmake Build

Für einen qmake-Build ändern Sie die Datei quickmulticastclient.pro auf folgende Weise:

CONFIG += qmltypes
QML_IMPORT_NAME = CoapClientModule
QML_IMPORT_MAJOR_VERSION = 1
    ...
qml_resources.files = \
    qmldir \
    Main.qml

qml_resources.prefix = /qt/qml/CoapClientModule

RESOURCES += qml_resources

Neue QML-Typen verwenden

Nun, da die C++-Klassen ordnungsgemäß in QML exponiert sind, können Sie die neuen Typen verwenden:

CoapMulticastClient {
    id: client
    onDiscovered: (resource) => { root.addResource(resource) }

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

Das Signal QmlCoapMulticastClient::finished() löst den Signalhandler onFinished aus, um den Status der Anfrage in der Benutzeroberfläche anzuzeigen. Beachten Sie, dass das Beispiel die Signale von QCoapClient nicht direkt verwendet, da die Signale error() und finished() ein QCoapReply als Parameter benötigen (das nicht in QML verfügbar ist), und das Beispiel nur den Fehlercode benötigt.

Der Konstruktor von QmlCoapMulticastClient leitet die Signale von QCoapClient an das Signal QmlCoapMulticastClient::finished() weiter:

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()));
                    reply->deleteLater();
                    if (m_reply == reply) {
                        m_reply = nullptr;
                        emit isDiscoveringChanged();
                    }
                } else {
                    qCWarning(lcCoapClient, "Something went wrong, received a null reply");
                }
            });

    connect(this, &QCoapClient::error, this,
            [this](QCoapReply *, QtCoap::Error err) {
                emit finished(static_cast<int>(err));
            });
}

Wenn die Schaltfläche Discover gedrückt wird, wird eine der überladenen Methoden discover() aufgerufen, basierend auf der ausgewählten Multicast-Gruppe:

Button {
    id: discoverButton
    text: client.isDiscovering ? qsTr("Stop Discovery") : qsTr("Discover")
    Layout.preferredWidth: 100

    onClicked: {
        if (client.isDiscovering) {
            client.stopDiscovery()
        } else {
            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);
        }
    }
}

Diese Überladung wird aufgerufen, wenn eine benutzerdefinierte Multicast-Gruppe oder eine Host-Adresse ausgewählt wird:

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

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

Und diese Überladung wird aufgerufen, wenn eine der vorgeschlagenen Multicast-Gruppen in der Benutzeroberfläche ausgewählt wird:

void QmlCoapMulticastClient::discover(QtCoap::MulticastGroup group, int port,
                                      const QString &discoveryPath)
{
    m_reply = QCoapClient::discover(group, port, discoveryPath);
    if (m_reply) {
        connect(m_reply, &QCoapResourceDiscoveryReply::discovered,
                this, &QmlCoapMulticastClient::onDiscovered);
        emit isDiscoveringChanged();
    } else {
        qCWarning(lcCoapClient, "Discovery request failed.");
    }
}

Das Signal QCoapResourceDiscoveryReply::discovered() liefert eine Liste von QCoapResources, die kein QML-Typ ist. Um die Ressourcen in QML verfügbar zu machen, leiten Sie jede Ressource in der Liste an das Signal QmlCoapMulticastClient::discovered() weiter, das stattdessen ein QmlCoapResource entgegennimmt:

void QmlCoapMulticastClient::onDiscovered(QCoapResourceDiscoveryReply *reply,
                                          const QList<QCoapResource> &resources)
{
    Q_UNUSED(reply)
    for (const auto &resource : resources)
        emit discovered(resource);
}

Die gefundenen Ressourcen werden der resourceModel der Listenansicht in der Benutzeroberfläche hinzugefügt:

function addResource(resource) {
    resourceModel.insert(0, {"host" : resource.host,
                             "path" : resource.path,
                             "title" : resource.title})
}

Während die Erkennung läuft, können Sie die Schaltfläche Stop Discovery drücken, um die Erkennung zu beenden. Intern geschieht dies durch den Abbruch der aktuellen Anfrage:

void QmlCoapMulticastClient::stopDiscovery()
{
    if (m_reply)
        m_reply->abortRequest();
}

Dateien:

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.