Quick CoAP Multicast Discovery Example▲
The Quick CoAP Multicast Discovery Example 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.
Running the 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:
-
Manually build and run CoAP servers using libcoap, Californium, or any other CoAP server implementation, which supports multicast and resource discovery features.
-
Use the ready Docker image available at Docker Hub, which builds and starts CoAP server based on Californium's multicast server example.
Using the Docker-based Test Server▲
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.
Creating a Client and Using It with QML▲
We create the QmlCoapMulticastClient class with the QCoapClient class as a base class:
class
QmlCoapMulticastClient : public
QCoapClient
{
Q_OBJECT
public
:
QmlCoapMulticastClient(QObject *
parent =
nullptr
);
Q_INVOKABLE void
discover(const
QString &
amp;host, int
port, const
QString &
amp;discoveryPath);
Q_INVOKABLE void
discover(QtCoap::
MulticastGroup group, int
port, const
QString &
amp;discoveryPath);
Q_SIGNALS
:
void
discovered(const
QmlCoapResource &
amp;resource);
void
finished(int
error);
public
slots:
void
onDiscovered(QCoapResourceDiscoveryReply *
reply, const
QList&
lt;QCoapResource&
gt; &
amp;resources);
}
;
In the main.cpp file, we register the QmlCoapMulticastClient class as a QML type:
qmlRegisterType&
lt;QmlCoapMulticastClient&
gt;("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
import
qtcoap.example.namespace
...
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() signal triggers the onError signal handler of CoapMulticastClient, and the QmlCoapMulticastClient::finished() signal triggers the 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.
In the 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
, &
amp;QCoapClient::
finished, this
,
[this
](QCoapReply *
reply) {
if
(reply)
emit finished(static_cast
&
lt;int
&
gt;(reply-&
gt;errorReceived()));
else
qCWarning(lcCoapClient, "Something went wrong, received a null reply"
);
}
);
}
When the 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 &
amp;host, int
port, const
QString &
amp;discoveryPath)
{
QUrl url;
url.setHost(host);
url.setPort(port);
QCoapResourceDiscoveryReply *
discoverReply =
QCoapClient::
discover(url, discoveryPath);
if
(discoverReply) {
connect(discoverReply, &
amp;QCoapResourceDiscoveryReply::
discovered,
this
, &
amp;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: