KNX Editor Example

 /****************************************************************************
 **
 ** Copyright (C) 2017 The Qt Company Ltd.
 ** Contact: https://www.qt.io/licensing/
 **
 ** This file is part of the QtKnx module.
 **
 ** $QT_BEGIN_LICENSE:BSD$
 ** Commercial License Usage
 ** Licensees holding valid commercial Qt licenses may use this file in
 ** accordance with the commercial license agreement provided with the
 ** Software or, alternatively, in accordance with the terms contained in
 ** a written agreement between you and The Qt Company. For licensing terms
 ** and conditions see https://www.qt.io/terms-conditions. For further
 ** information use the contact form at https://www.qt.io/contact-us.
 **
 ** BSD License Usage
 ** Alternatively, you may use this file under the terms of the BSD license
 ** as follows:
 **
 ** "Redistribution and use in source and binary forms, with or without
 ** modification, are permitted provided that the following conditions are
 ** met:
 **   * Redistributions of source code must retain the above copyright
 **     notice, this list of conditions and the following disclaimer.
 **   * Redistributions in binary form must reproduce the above copyright
 **     notice, this list of conditions and the following disclaimer in
 **     the documentation and/or other materials provided with the
 **     distribution.
 **   * Neither the name of The Qt Company Ltd nor the names of its
 **     contributors may be used to endorse or promote products derived
 **     from this software without specific prior written permission.
 **
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 **
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

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

 #include <QElapsedTimer>
 #include <QFileDialog>
 #include <QInputDialog>
 #include <QNetworkInterface>
 #include <QStandardItem>
 #include <QStandardPaths>

 Ui::MainWindow *MainWindow::s_ui { nullptr };

 static QString familyToString(QKnxNetIp::ServiceFamily id)
 {
     switch (id) {
     case QKnxNetIp::ServiceFamily::Core:
         return MainWindow::tr("Core");
     case QKnxNetIp::ServiceFamily::DeviceManagement:
         return MainWindow::tr("Device Management");
     case QKnxNetIp::ServiceFamily::IpTunneling:
         return MainWindow::tr("Tunnel");
     case QKnxNetIp::ServiceFamily::IpRouting:
         return MainWindow::tr("Routing");
     case QKnxNetIp::ServiceFamily::RemoteLogging:
         return MainWindow::tr("Remote Logging");
     case QKnxNetIp::ServiceFamily::RemoteConfigAndDiagnosis:
         return MainWindow::tr("Remote Configuration");
     case QKnxNetIp::ServiceFamily::ObjectServer:
         return MainWindow::tr("Object Server");
     case QKnxNetIp::ServiceFamily::Security:
         return MainWindow::tr("Security");
     default:
         break;
     }
     return MainWindow::tr("Unknown");
 }

 MainWindow::MainWindow(QWidget *parent)
     : QMainWindow(parent),
     ui(new Ui::MainWindow)
 {
     ui->setupUi(this);

     ui->tunneling->setEnabled(false);
     ui->deviceManagement->setEnabled(false);
     ui->tunnelingFeatures->setEnabled(false);

     ui->serverBox->addItem(tr("Press Scan button to discover KNX server(s)"));

     m_discoveryAgent.setTimeout(5000);
     connect(&m_discoveryAgent, &QKnxNetIpServerDiscoveryAgent::started, this, [&] {
         if (!ui)
             return;

         ui->scanButton->setEnabled(false);
         ui->checkboxNat->setEnabled(false);

         ui->serverDescription->clear();

         ui->serverBox->clear();
         ui->serverBox->setEnabled(false);

         auto firstItem = new QStandardItem(tr("Select a KNX server(s)"));
         qobject_cast<QStandardItemModel*>(ui->serverBox->model())->appendRow(firstItem);
         firstItem->setSelectable(false);
     });
     connect(&m_discoveryAgent, &QKnxNetIpServerDiscoveryAgent::finished, this, [&] {
         if (!ui)
             return;

         ui->scanButton->setEnabled(true);
         ui->checkboxNat->setEnabled(true);

         if (ui->serverBox->count() <= 1)
             ui->serverBox->setItemText(0, tr("Press Scan button to discover KNX server(s)"));
         else if (ui->serverBox->count() == 2)
             ui->serverBox->setCurrentIndex(1);

         ui->serverBox->setEnabled(true);
         newServerSelected(ui->serverBox->currentIndex());
     });
     connect(&m_discoveryAgent, &QKnxNetIpServerDiscoveryAgent::deviceDiscovered, this,
         &MainWindow::showServerAndServices);

     fillLocalIpBox();

     connect(ui->scanButton, &QPushButton::clicked, &m_discoveryAgent,
         QOverload<>::of(&QKnxNetIpServerDiscoveryAgent::start));

     connect(ui->checkboxNat, &QCheckBox::toggled, this, [&](bool checked) {
         if (!ui)
             return;

         ui->tunneling->setNatAware(checked);
         m_discoveryAgent.setNatAware(checked);
         ui->deviceManagement->setNatAware(checked);
         ui->tunnelingFeatures->setNatAware(checked);
     });

     connect(ui->localIpBox, QOverload<int>::of(&QComboBox::activated), this,
         &MainWindow::newIPAddressSelected);
     connect(ui->serverBox, QOverload<int>::of(&QComboBox::activated), this,
         &MainWindow::newServerSelected);

     connect(ui->actionExit, &QAction::triggered, this, &QApplication::quit);
     connect(ui->actionClear_Output, &QAction::triggered, ui->outputEdit, &QTextEdit::clear);

     connect(ui->actionClear_All, &QAction::triggered, ui->deviceManagement,
         &LocalDeviceManagement::clearLogging);
     connect(ui->actionClear_All, &QAction::triggered, ui->outputEdit, &QTextEdit::clear);
     connect(ui->actionClear_All, &QAction::triggered, ui->tunneling, &Tunneling::clearLogging);
     if (ui->localIpBox->count() == 2) {
         ui->localIpBox->setCurrentIndex(1);
         newIPAddressSelected(ui->localIpBox->currentIndex());
     }

     s_ui = ui;
     qInstallMessageHandler(MainWindow::messageHandler);
 }

 MainWindow::~MainWindow()
 {
     s_ui = nullptr;
     delete ui;
     ui = nullptr;
 }

 void MainWindow::newServerSelected(int serverBoxIndex)
 {
     if (serverBoxIndex < 1)
         return;

     auto info = ui->serverBox->itemData(serverBoxIndex).value<QKnxNetIpServerInfo>();
     bool version2Supported = false;
     ui->serverDescription->setText(tr("<html><head><style> th { text-align: left; } td.padding { "
         "padding-left: 10px; } </style></head> <body>"
         "   <table style=\"width:100%\">"
         "       <th>Device Information</th>"
         "           <tr><td class=\"padding\">Individual address: %1</td></tr>"
         "           <tr><td class=\"padding\">Server control endpoint: %2:%3</td></tr>"
         "           <tr></tr>"
         "       <tr><th>Supported services:</th></tr>"
         "           %4"
         "       </table>"
         "   </table>"
         "</body></html>")
         .arg(info.individualAddress().toString())
         .arg(info.controlEndpointAddress().toString()).arg(info.controlEndpointPort())
         .arg([&info, &version2Supported]() -> QString {
             QString value;
             const auto services = info.supportedServices();
             for (const auto &service : services) {
                 value.append(tr("<tr><td class=\"padding\">%1</td></th>")
                     .arg(tr("KNXnet/IP %1, Version: %2").arg(familyToString(service.ServiceFamily))
                         .arg(service.ServiceFamilyVersion)));
                 if (service.ServiceFamilyVersion >= 2)
                      version2Supported = true;
             }
             return value;
         }())
     );

     const auto &hpai = info.endpoint();
     const QKnxNetIpHpaiProxy endpoint(hpai);
     if (endpoint.isValid() && m_server != info) {
         m_server = info;

         ui->tunneling->setEnabled(true);
         ui->tunneling->setKnxNetIpServer(m_server);

         ui->deviceManagement->setEnabled(true);
         ui->deviceManagement->setKnxNetIpServer(m_server);

         ui->tunnelingFeatures->setEnabled(true);
         ui->tunnelingFeatures->setKnxNetIpServer(m_server);
     }
     ui->radioButtonTCP->setEnabled(version2Supported);
 }

 void MainWindow::newIPAddressSelected(int localIpBoxIndex)
 {
     if (localIpBoxIndex < 1)
         return;

     auto newAddress = QHostAddress(ui->localIpBox->itemData(localIpBoxIndex).toString());
     if (newAddress.isNull()) {
         ui->outputEdit->append(tr("Selected IP address is not valid"));
         return;
     }

     if (m_discoveryAgent.localAddress() == newAddress)
         return;

     ui->scanButton->setEnabled(true);
     ui->outputEdit->append(tr("Selected IP address: ") + newAddress.toString());

     m_discoveryAgent.stop();
     m_discoveryAgent.setLocalAddress(newAddress);

     ui->tunneling->setLocalAddress(newAddress);
     ui->deviceManagement->setLocalAddress(newAddress);
     ui->tunnelingFeatures->setLocalAddress(newAddress);
 }

 void MainWindow::showServerAndServices(const QKnxNetIpServerInfo &info)
 {
     ui->outputEdit->append(tr("Server Endpoint found"));
     ui->outputEdit->append(tr("Server's Multicast Address"));
     ui->outputEdit->append(info.controlEndpointAddress().toString());
     ui->outputEdit->append(tr("Server's Port"));
     ui->outputEdit->append(QString::number(info.controlEndpointPort()));
     ui->outputEdit->append(tr("The following services are supported:"));
     const auto services = info.supportedServices();
     for (const auto service : services) {
         ui->outputEdit->append(tr("    KNXnet/IP %1, Version: %2")
             .arg(familyToString(service.ServiceFamily)).arg(service.ServiceFamilyVersion));
     }

     ui->serverBox->addItem(tr("%1 (%2:%3)").arg(info.deviceName(), info.controlEndpointAddress()
         .toString()).arg(info.controlEndpointPort()), QVariant::fromValue(info));
 }

 void MainWindow::on_radioButtonTCP_toggled(bool checked)
 {
     ui->tunneling->setTcpEnable(checked);
     ui->deviceManagement->setTcpEnable(checked);
     ui->tunnelingFeatures->setTcpEnable(checked);
 }

 void MainWindow::on_actionEtsKeyringImport_triggered()
 {
     auto fileName = QFileDialog::getOpenFileName(this, tr("Import keyring file"),
         QStandardPaths::standardLocations(QStandardPaths::DesktopLocation).value(0),
         tr("KNX keyring file (*.knxkeys)"));

     if (fileName.isEmpty())
         return;

     bool ok;
     auto password = QInputDialog::getText(this, tr("Import keyring file"),
         tr("Keyring file password:"), QLineEdit::Normal, {}, &ok);
     if (!ok || password.isEmpty())
         return;

     auto mgmtConfigs = QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration
         ::Type::DeviceManagement, fileName, password.toUtf8(), true);
     ui->deviceManagement->onKeyringChanged(mgmtConfigs);

     for (auto &config : mgmtConfigs)
         config.setIndividualAddress({});

     auto tunnelConfigs = QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration
         ::Type::Tunneling, fileName, password.toUtf8(), true);
     ui->tunneling->onKeyringChanged(mgmtConfigs + tunnelConfigs);
     ui->tunnelingFeatures->onKeyringChanged(mgmtConfigs + tunnelConfigs);
 }

 void MainWindow::fillLocalIpBox()
 {
     auto firstItem = new QStandardItem(tr("Interface: IP address --Select One--"));
     qobject_cast<QStandardItemModel*>(ui->localIpBox->model())->appendRow(firstItem);
     firstItem->setSelectable(false);

     auto networkInterfaces = QNetworkInterface::allInterfaces();
     for (int i = 0; i < networkInterfaces.size(); i++) {
         auto addressList = networkInterfaces[i].addressEntries();
         for (int j = 0; j < addressList.size(); j++) {
             auto address = addressList[j].ip();
             if (address.isLoopback() || address.toIPv4Address() == 0)
                 continue;
             auto ipAddress = address.toString();
             ui->localIpBox->addItem(networkInterfaces[i].name() + ": " + ipAddress, ipAddress);
         }
     }
 }

 void MainWindow::messageHandler(QtMsgType type, const QMessageLogContext &ctx, const QString &msg)
 {
     class Uptime : public QElapsedTimer
     {
     public:
         Uptime() { start(); }
         quint64 lastEvent { 0 };
     };
     static Uptime uptime;

     auto msec = uptime.elapsed();
     if (s_ui) {
         s_ui->outputEdit->append(QStringLiteral("[elapsed: %1 msec, diff: %2 msec]\n    %3")
             .arg(msec) .arg(msec - uptime.lastEvent).arg(msg));
     }
     uptime.lastEvent = msec;

     if (type == QtFatalMsg) {
         auto oldMsgHandler = qInstallMessageHandler(0);
         qt_message_output(type, ctx, msg);
         qInstallMessageHandler(oldMsgHandler);
     }
 }