快捷CoAP多播发现
使用CoAP客户端和Qt Quick用户界面进行多播资源发现。
快捷CoAP多播发现示例演示了如何将QCoapClient注册为QML类型,并在Qt Quick应用程序中用于CoAP多播资源发现。
注意:Qt CoAP在其当前版本中不提供QML API。但是,您可以根据此示例使模块的C++类对QML可用。
运行示例
要从Qt Creator中运行示例,请打开欢迎模式,然后从示例中选择示例。有关更多信息,请访问构建和运行示例。
设置CoAP服务器
要运行示例应用程序,您首先需要设置并至少启动一个支持多播资源发现的CoAP服务器。您有以下选择
- 手动使用libcoap、Californium或其他任何支持多播和资源发现功能的CoAP服务器实现来构建和运行CoAP服务器。
- 使用Docker Hub上可用的预构建Docker映像,它基于Californium的多播服务器示例构建并启动CoAP服务器。
使用基于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
在此示例中,您需要将QCoapResource和QCoapClient类,以及QtCoap命名空间,公开给QML。为了实现这一点,请创建自定义封装类并使用特殊的注册宏。
创建一个QmlCoapResource
类,作为QCoapResource的封装。使用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()
方法
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."); } }
当在 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() 信号传递一个 QCoapResource 列表,这不是 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(); }
文件
© 2024 Qt 公司有限公司。此处包含的文档贡献是各自所有者的版权。本提供的文档是根据自由软件基金会发布的 GNU 自由文档许可证版本 1.3 术语许可的。Qt 和相应的商标是芬兰及其它国家/地区的 Qt 公司有限公司的 商标。所有其他商标均为各自所有者的财产。