Sur cette page

Découverte rapide de ressources multicast via CoAP

Utilisation du client CoAP pour la découverte de ressources en multidiffusion avec une interface utilisateur Qt Quick.

Fenêtre de découverte multicast affichant les ressources CoAP détectées

L'exemple « Découverte rapide de ressources multicast CoAP » montre comment enregistrer QCoapClient en tant que type QML et l'utiliser dans une application Qt Quick pour la découverte de ressources multicast CoAP.

Remarque : dans sa version actuelle,Qt CoAP ne fournit pas d’API QML. Cependant, vous pouvez rendre les classes C++ du module accessibles à QML, comme le montre cet exemple.

Exécution de l’exemple

Vous pouvez exécuter l’exemple à partir de :

Configuration d’un serveur CoAP

Pour exécuter l’application d’exemple, vous devez d’abord configurer et démarrer au moins un serveur CoAP prenant en charge la découverte de ressources en multidiffusion. Vous disposez des options suivantes :

  • Compiler et exécuter manuellement des serveurs CoAP à l’aide de libcoap, de Californium ou de toute autre implémentation de serveur CoAP prenant en charge les fonctionnalités de multidiffusion et de découverte de ressources.
  • Utiliser l’image Docker prête à l’emploi disponible sur Docker Hub, qui compile et démarre un serveur CoAP basé sur l’exemple de serveur multicast de Californium.
Utilisation du serveur de test basé sur Docker

La commande suivante récupère le conteneur Docker du serveur CoAP depuis Docker Hub et le démarre :

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

Remarque : vous pouvez exécuter plusieurs serveurs CoAP en multidiffusion (sur le même hôte ou sur d’autres hôtes du réseau) en passant un --name différent à la commande ci-dessus.

Pour arrêter le conteneur Docker après utilisation, commencez par récupérer l’ID du conteneur en exécutant la commande ` docker ps `. La sortie ressemblera à ceci :

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

Ensuite, utilisez cet ID pour arrêter le conteneur :

docker stop <container_id>

Exposition des classes C++ vers QML

Dans cet exemple, vous devez exposer les classes ` QCoapResource ` et ` QCoapClient `, ainsi que l’espace de noms ` QtCoap `, à QML. Pour ce faire, créez des classes de wrapper personnalisées et utilisez les macros d’enregistrement spéciales.

Créez la classe ` QmlCoapResource ` comme wrapper autour de ` QCoapResource`. Utilisez la macro ` Q_PROPERTY ` pour rendre plusieurs propriétés accessibles depuis QML. La classe n’a pas besoin d’être directement instanciable depuis QML ; utilisez donc la macro ` QML_ANONYMOUS ` pour l’enregistrer.

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(); }
};

Ensuite, créez la classe ` QmlCoapMulticastClient ` en utilisant la classe ` QCoapClient ` comme classe de base. Utilisez la macro ` Q_PROPERTY ` pour exposer une propriété personnalisée, et créez également plusieurs méthodes ` Q_INVOKABLE `. La propriété et les méthodes appelables sont toutes deux accessibles depuis QML. Contrairement à ` QmlCoapResource`, vous souhaitez pouvoir créer cette classe depuis QML ; utilisez donc la macro ` QML_NAMED_ELEMENT ` pour enregistrer la classe dans QML.

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;
};

Enfin, enregistrez l’espace de noms QtCoap afin de pouvoir utiliser les énumérations qui y sont fournies :

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

Modification des fichiers de compilation

Pour que les types personnalisés soient disponibles depuis QML, mettez à jour les fichiers du système de compilation en conséquence.

Construction CMake

Pour une compilation basée sur CMake, ajoutez ce qui suit au fichier CMakeLists.txt:

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

Pour une compilation qmake, modifiez le fichier quickmulticastclient.pro de la manière suivante :

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

Utilisation des nouveaux types QML

Désormais, les classes C++ étant correctement exposées à QML, vous pouvez utiliser les nouveaux types :

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)
    }
}

Le signal ` QmlCoapMulticastClient::finished() ` déclenche le gestionnaire de signal ` onFinished `, afin d’afficher l’état de la requête dans l’interface utilisateur. Notez que l’exemple n’utilise pas directement les signaux de ` QCoapClient`, car les signaux ` error()` et ` finished()` prennent tous deux un ` QCoapReply ` en paramètre (qui n’est pas exposé à QML), et l’exemple ne nécessite que le code d’erreur.

Le constructeur de ` QmlCoapMulticastClient` transmet les signaux de ` QCoapClient` au signal ` QmlCoapMulticastClient::finished() ` :

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));
            });
}

Lorsque l’on appuie sur le bouton « Discover », l’une des méthodes surchargées de ` discover() ` est appelée, en fonction du groupe de multidiffusion sélectionné :

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);
        }
    }
}

Cette surcharge est appelée lorsqu’un groupe de multidiffusion personnalisé ou une adresse hôte est sélectionné :

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.");
    }
}

Et cette surcharge est appelée lorsqu’un des groupes de multidiffusion suggérés est sélectionné dans l’interface utilisateur :

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.");
    }
}

Le signal ` QCoapResourceDiscoveryReply::discovered()` renvoie une liste d’ QCoapResource, qui n’est pas un type QML. Pour rendre ces ressources disponibles dans QML, transmettez chaque ressource de la liste au signal ` QmlCoapMulticastClient::discovered() `, qui accepte à la place un ` QmlCoapResource ` :

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

Les ressources détectées sont ajoutées à la resourceModel de la vue de liste dans l’interface utilisateur :

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

Pendant que la découverte est en cours, appuyez sur le bouton « Stop Discovery » pour l'interrompre. En interne, cela s'effectue en annulant la requête en cours :

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

Fichiers :

© 2026 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.