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); } }