クイックCoAPマルチキャストディスカバリー

CoAP クライアントを Qt Quick ユーザーインターフェースでマルチキャストリソースディスカバリーに使用する。

Quick CoAP Multicast Discovery の例では、QCoapClient を QML タイプとして登録し、Qt Quick アプリケーションで CoAP マルチキャストリソースディスカバリーに使用する方法を示しています。

注意: Qt CoAP は現在のバージョンでは QML API を提供していません。しかし、この例のように、モジュールの C++ クラスを QML で利用できるようにすることは可能です。

例の実行

Qt Creator からサンプルを実行するには、Welcome モードを開き、Examples からサンプルを選択します。詳しくは、Building and Running an Example を参照してください。

CoAP サーバーのセットアップ

サンプル・アプリケーションを実行するには、まずマルチキャスト・リソース・ディスカバリーをサポートする CoAP サーバーを少なくとも 1 つセットアップして起動する必要があります。以下のオプションがあります:

  • libcoapCalifornium、またはマルチキャストとリソース検出機能をサポートするその他のCoAPサーバー実装を使用して、CoAPサーバーを手動で構築して実行する。
  • Californiumのマルチキャストサーバーの例に基づいてCoAPサーバーを構築して起動する、Docker Hubで入手可能な準備のできたDockerイメージを使用する。
Dockerベースのテストサーバーの使用

以下のコマンドは、Docker HubからCoAPサーバー用のDockerコンテナを取り出し、それを起動します:

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

注: 上記のコマンドに異なる--name を渡すことで、複数のマルチキャスト CoAP サーバーを(同じホストやネットワーク内の他のホストで)実行することができます。

使用後に docker コンテナを終了するには、まずdocker ps コマンドを実行してコンテナの ID を取得する。出力はこのようになる:

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

その後、この ID を使用してコンテナを停止する:

docker stop <container_id>

C++ クラスを QML に公開する

この例では、QCoapResourceQCoapClient のクラスと、QtCoap の名前空間を QML に公開する必要があります。そのためには、カスタムラッパークラスを作成し、特別な登録マクロを使用します。

QCoapResource のラッパーとしてQmlCoapResource クラスを作成します。Q_PROPERTY マクロを使い、QML からいくつかのプロパティにアクセスできるようにします。このクラスはQMLから直接インスタンス化する必要はありませんので、QML_ANONYMOUS マクロを使って登録してください。

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

その後、QCoapClient クラスを基底クラスとして、QmlCoapMulticastClient クラスを作成します。Q_PROPERTY マクロを使ってカスタムプロパティを公開し、Q_INVOKABLE メソッドをいくつか作成します。プロパティも呼び出し可能なメソッドも QML からアクセスできます。QmlCoapResource とは異なり、このクラスは QML から作成できるようにしたいので、QML_NAMED_ELEMENT マクロを使用して 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;
};

最後に、QtCoap 名前空間を登録し、そこで提供される列挙型を使用できるようにします:

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

ビルドファイルの調整

QMLからカスタム型を利用できるようにするために、ビルドシステムファイルを適宜更新してください。

CMakeビルド

CMakeベースのビルドでは、CMakeLists.txt に以下を追加してください:

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

qmakeビルドの場合は、quickmulticastclient.pro ファイルを以下のように変更してください:

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

新しいQML型の使用

これで、C++クラスがQMLに正しく公開され、新しい型が使えるようになりました:

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

QmlCoapMulticastClient::finished() シグナルはonFinished シグナルハンドラをトリガーし、リクエストのステータスを UI に表示します。QCoapClient error() とfinished() シグナルはどちらもQCoapReply をパラメータとして受け取り(これは QML に公開されていません)、この例ではエラーコードだけが必要だからです。

QmlCoapMulticastClient のコンストラクタはQCoapClient のシグナルを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));
            });
}

Discover ボタンが押されると、選択されたマルチキャストグループに基づいて、オーバーロードされたdiscover() メソッドの1つが呼び出されます:

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

このオーバーロードは、カスタムマルチキャストグループまたはホストアドレスが選択されたときに呼び出される:

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

このオーバーロードは、カスタムマルチキャストグループまたはホストアドレスが選択されたときに呼び出されます。また、このオーバーロードは、提案されたマルチキャストグループの1つがUIで選択されたときに呼び出されます:

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

QCoapResourceDiscoveryReply::discovered()シグナルはQCoapResourcesのリストを配信します。これはQMLの型ではありません。QMLでリソースを利用できるようにするには、リストの各リソースをQmlCoapMulticastClient::discovered() シグナルに転送します。 シグナルはQmlCoapResource を代わりに受け取ります:

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

発見されたリソースはUIのリストビューのresourceModel

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

ディスカバリーの進行中に、Stop Discovery ボタンを押すと、ディスカバリーを停止することができます。内部的には、現在のリクエストを中止することで行われます:

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

ファイル

ここに含まれるドキュメントの著作権はそれぞれの所有者に帰属します 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。