Simple CoAP Client

 // Copyright (C) 2019 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

 #include "mainwindow.h"
 #include "optiondialog.h"
 #include "ui_mainwindow.h"

 #include <QCoapClient>
 #include <QCoapResourceDiscoveryReply>
 #include <QCoapReply>
 #include <QDateTime>
 #include <QFileDialog>
 #include <QHostInfo>
 #include <QMessageBox>
 #include <QMetaEnum>
 #include <QNetworkInterface>

 using namespace Qt::StringLiterals;

 MainWindow::MainWindow(QWidget *parent) :
     QMainWindow(parent),
     ui(new Ui::MainWindow)
 {
     m_client = new QCoapClient(QtCoap::SecurityMode::NoSecurity, this);
     connect(m_client, &QCoapClient::finished, this, &MainWindow::onFinished);
     connect(m_client, &QCoapClient::error, this, &MainWindow::onError);

     ui->setupUi(this);

     ui->methodComboBox->addItem(tr("Get"), QVariant::fromValue(QtCoap::Method::Get));
     ui->methodComboBox->addItem(tr("Put"), QVariant::fromValue(QtCoap::Method::Put));
     ui->methodComboBox->addItem(tr("Post"), QVariant::fromValue(QtCoap::Method::Post));
     ui->methodComboBox->addItem(tr("Delete"), QVariant::fromValue(QtCoap::Method::Delete));

     fillHostSelector();
     ui->hostComboBox->setFocus();
 }

 MainWindow::~MainWindow()
 {
     delete ui;
 }

 void MainWindow::fillHostSelector()
 {
     const auto networkInterfaces = QNetworkInterface::allInterfaces();
     for (const auto &interface : networkInterfaces)
         for (const auto &address : interface.addressEntries())
             ui->hostComboBox->addItem(address.ip().toString());
 }

 void MainWindow::addMessage(const QString &message, bool isError)
 {
     const QString content = "--------------- %1 ---------------\n%2\n\n"_L1
                                 .arg(QDateTime::currentDateTime().toString(), message);
     ui->textEdit->setTextColor(isError ? Qt::red : Qt::black);
     ui->textEdit->insertPlainText(content);
     ui->textEdit->ensureCursorVisible();
 }

 void MainWindow::onFinished(QCoapReply *reply)
 {
     if (reply->errorReceived() == QtCoap::Error::Ok)
         addMessage(reply->message().payload());
 }

 static QString errorMessage(QtCoap::Error errorCode)
 {
     const auto error = QMetaEnum::fromType<QtCoap::Error>().valueToKey(static_cast<int>(errorCode));
     return MainWindow::tr("Request failed with error: %1\n").arg(error);
 }

 void MainWindow::onError(QCoapReply *reply, QtCoap::Error error)
 {
     const auto errorCode = reply ? reply->errorReceived() : error;
     addMessage(errorMessage(errorCode), true);
 }

 void MainWindow::onDiscovered(QCoapResourceDiscoveryReply *reply, QList<QCoapResource> resources)
 {
     if (reply->errorReceived() != QtCoap::Error::Ok)
         return;

     QString message;
     for (const auto &resource : std::as_const(resources)) {
         ui->resourceComboBox->addItem(resource.path());
         message += tr("Discovered resource: \"%1\" on path %2\n")
                         .arg(resource.title(), resource.path());
     }
     addMessage(message);
 }

 void MainWindow::onNotified(QCoapReply *reply, const QCoapMessage &message)
 {
     if (reply->errorReceived() == QtCoap::Error::Ok) {
         addMessage(tr("Received observe notification with payload: %1")
                         .arg(QString::fromUtf8(message.payload())));
     }
 }

 static QString tryToResolveHostName(const QString hostName)
 {
     const auto hostInfo = QHostInfo::fromName(hostName);
     if (!hostInfo.addresses().empty())
         return hostInfo.addresses().first().toString();

     return hostName;
 }

 void MainWindow::on_runButton_clicked()
 {
     const auto msgType = ui->msgTypeCheckBox->isChecked() ? QCoapMessage::Type::Confirmable
                                                           : QCoapMessage::Type::NonConfirmable;
     QUrl url;
     url.setHost(tryToResolveHostName(ui->hostComboBox->currentText()));
     url.setPort(ui->portSpinBox->value());
     url.setPath(ui->resourceComboBox->currentText());

     QCoapRequest request(url, msgType);
     for (const auto &option : std::as_const(m_options))
         request.addOption(option);
     m_options.clear();

     const auto method = ui->methodComboBox->currentData(Qt::UserRole).value<QtCoap::Method>();
     switch (method) {
     case QtCoap::Method::Get:
         m_client->get(request);
         break;
     case QtCoap::Method::Put:
         m_client->put(request, m_currentData);
         break;
     case QtCoap::Method::Post:
         m_client->post(request, m_currentData);
         break;
     case QtCoap::Method::Delete:
         m_client->deleteResource(request);
         break;
     default:
         break;
     }
     m_currentData.clear();
 }

 void MainWindow::on_discoverButton_clicked()
 {
     QUrl url;
     url.setHost(tryToResolveHostName(ui->hostComboBox->currentText()));
     url.setPort(ui->portSpinBox->value());

     QCoapResourceDiscoveryReply *discoverReply =
             m_client->discover(url, ui->discoveryPathEdit->text());
     if (discoverReply) {
         connect(discoverReply, &QCoapResourceDiscoveryReply::discovered,
                 this, &MainWindow::onDiscovered);
     } else {
         QMessageBox::critical(this, tr("Error"),
                               tr("Something went wrong, discovery request failed."));
     }
 }

 void MainWindow::on_observeButton_clicked()
 {
     QUrl url;
     url.setHost(tryToResolveHostName(ui->hostComboBox->currentText()));
     url.setPort(ui->portSpinBox->value());
     url.setPath(ui->resourceComboBox->currentText());

     QCoapReply *observeReply = m_client->observe(url);
     if (!observeReply) {
         QMessageBox::critical(this, tr("Error"),
                               tr("Something went wrong, observe request failed."));
         return;
     }

     connect(observeReply, &QCoapReply::notified, this, &MainWindow::onNotified);

     ui->cancelObserveButton->setEnabled(true);
     connect(ui->cancelObserveButton, &QPushButton::clicked, this, [this, url]() {
         m_client->cancelObserve(url);
         ui->cancelObserveButton->setEnabled(false);
     });
 }

 void MainWindow::on_addOptionsButton_clicked()
 {
     OptionDialog dialog(m_options);
     if (dialog.exec() == QDialog::Accepted)
         m_options = dialog.options();
 }

 void MainWindow::on_contentButton_clicked()
 {
     QFileDialog dialog(this);
     dialog.setFileMode(QFileDialog::AnyFile);
     if (!dialog.exec())
         return;

     const auto fileName = dialog.selectedFiles().back();
     QFile file(fileName);
     if (!file.open(QIODevice::ReadOnly)) {
         QMessageBox::critical(this, tr("Error"), tr("Failed to read from file %1").arg(fileName));
         return;
     }

     m_currentData = file.readAll();
 }

 void MainWindow::on_resourceComboBox_editTextChanged(const QString &text)
 {
     ui->observeButton->setEnabled(!text.isEmpty());
 }

 void MainWindow::on_methodComboBox_currentIndexChanged(int index)
 {
     Q_UNUSED(index);

     const auto method = ui->methodComboBox->currentData(Qt::UserRole).value<QtCoap::Method>();
     ui->contentButton->setEnabled(method == QtCoap::Method::Put || method == QtCoap::Method::Post);
 }