采用 Qt Quick 用户界面将 CoAP 客户端用于多点播送资源探索。
The 快速 CoAP 多点播送探索范例 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.
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:
The following command pulls the docker container for the CoAP server from the Docker Hub and starts it:
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.
创建
QmlCoapMulticastClient
类采用
QCoapClient
class as a base class:
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 QVector<QCoapResource> &resources); };
In the main.cpp file, we register the
QmlCoapMulticastClient
class as a QML type:
qmlRegisterType<QmlCoapMulticastClient>("CoapMulticastClient", 1, 0, "CoapMulticastClient");
We also register the QtCoap namespace, to be able to use it in QML code:
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:
... import CoapMulticastClient 1.0 import qtcoap.example.namespace 1.0 ... 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()
信号触发
onError
signal handler of
CoapMulticastClient
,和
QmlCoapMulticastClient::finished()
信号触发
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.
在
QmlCoapMulticastClient
's constructor, we arrange for the
QCoapClient::finished()
signal to be forwarded to the
QmlCoapMulticastClient::finished()
signal:
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"); }); }
当
Discover
button is pressed, we invoke one of the overloaded
discover()
methods, based on the selected multicast group:
... 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:
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:
void QmlCoapMulticastClient::discover(QtCoap::MulticastGroup group, int port, const QString &discoveryPath) { QCoapResourceDiscoveryReply *discoverReply = QCoapClient::discover(group, port, discoveryPath); if (discoverReply) { connect(discoverReply, &QCoapResourceDiscoveryReply::discovered, this, &QmlCoapMulticastClient::onDiscovered); } else { qCWarning(lcCoapClient, "Discovery request failed."); } }
The
QCoapClient::discovered()
signal delivers a list of
QCoapResources
, which is not a QML type. To make the resources available in QML, we forward each resource in the list to the
QmlCoapMulticastClient::discovered()
signal, which takes a
QmlCoapResource
instead:
void QmlCoapMulticastClient::onDiscovered(QCoapResourceDiscoveryReply *reply, const QVector<QCoapResource> &resources) { Q_UNUSED(reply) for (auto resource : resources) emit discovered(resource); }
QmlCoapResource
is a wrapper around
QCoapResource
, to make some of its properties available in QML:
class QmlCoapResource : public QCoapResource { Q_GADGET Q_PROPERTY(QString title READ title) Q_PROPERTY(QString host READ hostStr) Q_PROPERTY(QString path READ path) public: QmlCoapResource() : QCoapResource() {} QmlCoapResource(const QCoapResource &resource) : QCoapResource(resource) {} QString hostStr() const { return host().toString(); } };
The discovered resources are added to the
resourceModel
of the list view in the UI:
... function addResource(resource) { resourceModel.insert(0, {"host" : resource.host, "path" : resource.path, "title" : resource.title}) } ...
文件: