diff options
author | Karsten Heimrich <karsten.heimrich@qt.io> | 2019-04-23 11:26:47 +0200 |
---|---|---|
committer | Karsten Heimrich <karsten.heimrich@qt.io> | 2019-05-04 00:07:34 +0000 |
commit | 4443f12461fc9cab42f4229454a390bd9e1ec4a4 (patch) | |
tree | 3a140249bc438ab63e366d347b8c8a1fdfa00b7e /examples | |
parent | 60d096199945d39aad92384e3ba9a47be406d650 (diff) |
Introduce tunneling feature example and documentationv5.13.0-rc3v5.13.0-rc2v5.13.0-rc1v5.13.0-beta4v5.13.05.13.0
Change-Id: I8d082546f4fb62b3a7a3bc6622df7e24d9459d86
Reviewed-by: Karsten Heimrich <karsten.heimrich@qt.io>
Diffstat (limited to 'examples')
-rw-r--r-- | examples/knx/doc/feature.qdoc | 144 | ||||
-rw-r--r-- | examples/knx/doc/images/features.png | bin | 0 -> 24140 bytes | |||
-rw-r--r-- | examples/knx/feature/deviceitem.cpp | 66 | ||||
-rw-r--r-- | examples/knx/feature/deviceitem.h | 70 | ||||
-rw-r--r-- | examples/knx/feature/feature.pro | 18 | ||||
-rw-r--r-- | examples/knx/feature/main.cpp | 62 | ||||
-rw-r--r-- | examples/knx/feature/mainwindow.cpp | 390 | ||||
-rw-r--r-- | examples/knx/feature/mainwindow.h | 113 | ||||
-rw-r--r-- | examples/knx/feature/mainwindow.ui | 332 | ||||
-rw-r--r-- | examples/knx/knx.pro | 2 |
10 files changed, 1196 insertions, 1 deletions
diff --git a/examples/knx/doc/feature.qdoc b/examples/knx/doc/feature.qdoc new file mode 100644 index 0000000..8daa8e5 --- /dev/null +++ b/examples/knx/doc/feature.qdoc @@ -0,0 +1,144 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** 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. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: https://www.gnu.org/licenses/fdl-1.3.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example feature + \title KNX Tunneling Features Example + \ingroup qtknx-examples + + \brief A KNX client for handling KNXnet/IP tunneling features. + + \image features.png "KNX tunneling features example" + + The \e{KNX Tunneling Features} user interface contains various + Qt Widgets, most prominently a \l QTreeWidget to display detailed + information about sent and received KNX tunneling feature messages. + + To get started, users select one of the network interfaces on their + machine in the \uicontrol {Interface} field. Once that is done, the + application automatically performs a continuous search for available + KNXnet/IP devices and displays the results in the \uicontrol {Device} + field. + + To connect to a KKXnet/IP device, either the one preselected in the + \uicontrol {Device} can be used or a different one must be chosen from + the list of discovered devices. + + The application also supports KNXnet/IP secure devices, but to be able to + connect to such a device, a KNX ETS keyring file needs to be imported via + the \uicontrol {File} menu. + + Once a connection is successfully established, the user has the possibility + to send tunneling feature get or set commands and to monitor incoming tunnel + feature info messages. + + The application consists of two classes: + + \list + \li \c MainWindow is a \l QMainWindow that renders the general layout + of the application. + \li \c DeviceItem is a \l QStandardItem that is used to display + and store information about discovered KNXnet/IP devices. + \endlist + + \section1 Main Window Class Definition and Implementation + + \quotefromfile feature/mainwindow.h + \skipto class MainWindow : + \printuntil /^\}/ + + The \c MainWindow class uses a \l QKnxNetIpServerDiscoveryAgent instance + that allows discovering KNXnet/IP servers by sending continuous search + requests to the network that the client is connected to. It also saves an + instance of the \l QKnxNetIpTunnel used to establish the connection to the + KNX network and a list of imported KNX \l QKnxNetIpSecureConfiguration + secure configurations. + + There are signal handlers installed for every signal emitted by the + \l QKnxNetIpTunnel. Here is an example of the setup capturing the + signals emitted when an event occurs targeting the KNXnet/IP connection. + In this specific example, we will also see how to set up the KNXnet/IP + tunnel and connect to the KNXnet/IP device: + + \quotefromfile feature/mainwindow.cpp + \skipto MainWindow::MainWindow + \printuntil { + \dots + \skipto QKnxNetIpTunnel::connected + \printuntil MainWindow::onErrorOccurred); + \dots + \skipto /^\}/ + \printuntil /^\}/ + \skipto void MainWindow::on_connection_clicked + \printuntil /^\}/ + + The \c QKnxNetIpServerDiscoveryAgent is initialized and started after the + user interface has been set up and the necessary tunnel connections have + been made. Here is the code snippet doing it: + + \quotefromfile feature/mainwindow.cpp + \skipto MainWindow::MainWindow + \printuntil { + \dots + \skipto m_discoveryAgent.setTimeout + \printuntil /^\}/ + + There is a signal handler installed for the device discovered signal + emitted by the discovery agent. + When the signal \l QKnxNetIpServerDiscoveryAgent::deviceDiscovered is + triggered, the function \c MainWindow::onDeviceDiscovered() is called. + It adds a new device item to the \uicontrol {Device} if it is not already + there. + + \quotefromfile device/mainwindow.cpp + \skipto void MainWindow::onDeviceDiscovered + \printuntil /^\}/ + + At this point, users can select one of the available devices to establish + a connection, create and send the different types of frames or monitor the + KNX tunneling feature info messages. + The \c MainWindow::on_devices_currentIndexChanged method saves the selected + KNXnet/IP device in the the \c MainWindow instance. + + In this last example, after the user has triggered the \uicontrol {Read} + button and a valid tunneling feature response was received, the function + \c MainWindow::setText() is called and the frame content gets extracted + and visually processed: + + \quotefromfile feature/mainwindow.cpp + \skipto MainWindow::setText + \printuntil /^\}/ + + \section1 The Main Function + + The KNX tunneling feature example \c main() function does not have any + special handling. It looks like the main function for any Qt application: + + \quotefromfile feature/main.cpp + \skipto #include + \printuntil /^\}/ +*/ diff --git a/examples/knx/doc/images/features.png b/examples/knx/doc/images/features.png Binary files differnew file mode 100644 index 0000000..b4e0f25 --- /dev/null +++ b/examples/knx/doc/images/features.png diff --git a/examples/knx/feature/deviceitem.cpp b/examples/knx/feature/deviceitem.cpp new file mode 100644 index 0000000..c353d1b --- /dev/null +++ b/examples/knx/feature/deviceitem.cpp @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "deviceitem.h" + +DeviceItem::DeviceItem(const QKnxNetIpServerInfo &info) + : QStandardItem(info.deviceName()) + , m_serverInfo(info) +{} + +DeviceItem *DeviceItem::clone() const +{ + return new DeviceItem(m_serverInfo); +} + +const QKnxNetIpServerInfo &DeviceItem::info() const +{ + return m_serverInfo; +} diff --git a/examples/knx/feature/deviceitem.h b/examples/knx/feature/deviceitem.h new file mode 100644 index 0000000..5f59407 --- /dev/null +++ b/examples/knx/feature/deviceitem.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +******************************************************************************/ + +#ifndef DEVICEITEM_H +#define DEVICEITEM_H + +#include <QtGui> +#include <QtKnx> + +class DeviceItem : public QStandardItem +{ +public: + DeviceItem(const QKnxNetIpServerInfo &info); + ~DeviceItem() = default; + + DeviceItem *clone() const override; + const QKnxNetIpServerInfo &info() const; + +private: + QKnxNetIpServerInfo m_serverInfo; +}; + +#endif // DEVICEITEM_H diff --git a/examples/knx/feature/feature.pro b/examples/knx/feature/feature.pro new file mode 100644 index 0000000..f75ad7a --- /dev/null +++ b/examples/knx/feature/feature.pro @@ -0,0 +1,18 @@ +TEMPLATE = app +TARGET = feature +INCLUDEPATH += . + +CONFIG += c++11 +QT += knx widgets network core + +FORMS += mainwindow.ui + +HEADERS += deviceitem.h \ + mainwindow.h + +SOURCES += deviceitem.cpp \ + main.cpp \ + mainwindow.cpp + +target.path = $$[QT_INSTALL_EXAMPLES]/knx/feature +INSTALLS += target diff --git a/examples/knx/feature/main.cpp b/examples/knx/feature/main.cpp new file mode 100644 index 0000000..e3e9a74 --- /dev/null +++ b/examples/knx/feature/main.cpp @@ -0,0 +1,62 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 <QApplication> + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/examples/knx/feature/mainwindow.cpp b/examples/knx/feature/mainwindow.cpp new file mode 100644 index 0000000..094baab --- /dev/null +++ b/examples/knx/feature/mainwindow.cpp @@ -0,0 +1,390 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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 "deviceitem.h" + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + + for (int i = 0; i < ui->communication->columnCount(); ++i) + ui->communication->resizeColumnToContents(i); + + setupInterfaces(); + populateServiceTypesComboBox(); + populateInterfaceFeaturesComboBox(); + + connect(ui->actionExit, &QAction::triggered, this, &QApplication::quit); + connect(ui->actionClear, &QAction::triggered, ui->communication, &QTreeWidget::clear); + + connect(&m_tunnel, &QKnxNetIpTunnel::connected, this, &MainWindow::onConnected); + connect(&m_tunnel, &QKnxNetIpTunnel::disconnected, this, &MainWindow::onDisconnected); + connect(&m_tunnel, &QKnxNetIpTunnel::tunnelingFeatureInfoReceived, this, + &MainWindow::onFeatureInfoReceived); + connect(&m_tunnel, &QKnxNetIpTunnel::tunnelingFeatureResponseReceived, this, + &MainWindow::onFeatureResponseReceived); + connect(&m_tunnel, &QKnxNetIpTunnel::errorOccurred, this, &MainWindow::onErrorOccurred); + + connect(ui->communication, &QTreeWidget::currentItemChanged, this, + [&](QTreeWidgetItem *current, QTreeWidgetItem * /* previuos */) { + m_current = current; + }); + + m_discoveryAgent.setTimeout(-1); + m_discoveryAgent.setSearchFrequency(6); + connect(&m_discoveryAgent, &QKnxNetIpServerDiscoveryAgent::deviceDiscovered, this, + &MainWindow::onDeviceDiscovered); + m_discoveryAgent.setDiscoveryMode(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV2); + + m_discoveryAgent.start(); +} + +MainWindow::~MainWindow() +{ + m_discoveryAgent.stop(); + + delete ui; + ui = nullptr; + delete m_device; +} + +void MainWindow::onConnected() +{ + toggleUi(true); + ui->connection->setText(tr("Disconnect")); + + m_last = new QTreeWidgetItem(ui->communication, m_last); + m_last->setText(0, tr("Successfully connected to: %1 (%2 : %3)") + .arg(m_device->info().deviceName()) + .arg(m_device->info().controlEndpointAddress().toString()) + .arg(m_device->info().controlEndpointPort())); + m_last->setFirstColumnSpanned(true); +} + +void MainWindow::onDisconnected() +{ + toggleUi(false); + ui->connection->setText(tr("Connect")); + + m_last = new QTreeWidgetItem(ui->communication, m_last); + m_last->setText(0, tr("Successfully disconnected from: %1 (%2 : %3)") + .arg(m_device->info().deviceName()) + .arg(m_device->info().controlEndpointAddress().toString()) + .arg(m_device->info().controlEndpointPort())); + m_last->setFirstColumnSpanned(true); +} + +void MainWindow::onDeviceDiscovered(QKnxNetIpServerInfo info) +{ + if (ui->devices->findText(info.deviceName()) == -1) + qobject_cast<QStandardItemModel*>(ui->devices->model())->appendRow(new DeviceItem(info)); + + if (m_tunnel.state() == QKnxNetIpTunnel::State::Disconnected) + ui->devices->setEnabled(bool(ui->devices->count())); +} + +void MainWindow::onErrorOccurred(QKnxNetIpEndpointConnection::Error error, QString errorString) +{ + m_last = new QTreeWidgetItem(ui->communication, m_last); + const auto metaEnum = QMetaEnum::fromType<QKnxNetIpEndpointConnection::Error>(); + m_last->setText(0, tr("Error occurred: %1 (%2).").arg(errorString) + .arg(metaEnum.valueToKey(int(error)))); + m_last->setFirstColumnSpanned(true); +} + +void MainWindow::on_actionImport_triggered() +{ + const 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; + const auto password = QInputDialog::getText(this, tr("Import keyring file"), + tr("Keyring file password:"), QLineEdit::Normal, {}, &ok); + if (!ok || password.isEmpty()) + return; + + m_secureConfigs = QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration + ::Type::DeviceManagement, fileName, password.toUtf8(), true); + for (auto &config : m_secureConfigs) + config.setIndividualAddress({}); + + m_secureConfigs += QKnxNetIpSecureConfiguration::fromKeyring(QKnxNetIpSecureConfiguration + ::Type::Tunneling, fileName, password.toUtf8(), true); + + on_devices_currentIndexChanged(ui->devices->currentIndex()); +} + +void MainWindow::on_devices_currentIndexChanged(int /* index */) +{ + delete m_device; + ui->secureConfigs->clear(); + + const auto model = qobject_cast<QStandardItemModel*>(ui->devices->model()); + m_device = static_cast<DeviceItem *> (model->item(ui->devices->currentIndex()))->clone(); + + if (m_device) { + const auto deviceInfo = m_device->info(); + for (int i = 0; i < m_secureConfigs.size(); ++i) { + const auto &config = m_secureConfigs[i]; + if (deviceInfo.individualAddress() != config.host()) + continue; + + const auto ia = config.individualAddress(); + ui->secureConfigs->addItem(tr("User ID: %1 (Individual Address: %2)") + .arg(config.userId()) + .arg(ia.isValid() ? ia.toString() : tr("No specific address")), i); + } + } + + ui->secureConfigs->setEnabled(bool(ui->secureConfigs->count()) + && ui->secureSession->isChecked()); + ui->secureSession->setEnabled(bool(ui->secureConfigs->count())); +} + +void MainWindow::on_serviceTypes_currentIndexChanged(int index) +{ + bool enable = m_tunnel.state() == QKnxNetIpTunnel::State::Connected; + const auto type = ui->serviceTypes->itemData(index).value<QKnxNetIp::ServiceType>(); + + ui->sendRead->setEnabled(enable && type == QKnxNetIp::ServiceType::TunnelingFeatureGet); + ui->sendWrite->setEnabled(enable && type == QKnxNetIp::ServiceType::TunnelingFeatureSet); + ui->data->setEnabled(enable && type == QKnxNetIp::ServiceType::TunnelingFeatureSet); +} + +void MainWindow::on_sendRead_clicked() +{ + const auto feature = ui->interfaceFeatures->itemData(ui->interfaceFeatures->currentIndex()) + .value<QKnx::InterfaceFeature>(); + + m_tunnel.sendTunnelingFeatureGet(feature); + populateFrame(QKnxNetIp::ServiceType::TunnelingFeatureGet, feature, {}); +} + +void MainWindow::on_sendWrite_clicked() +{ + const auto feature = ui->interfaceFeatures->itemData(ui->interfaceFeatures->currentIndex()) + .value<QKnx::InterfaceFeature>(); + + const auto data = QKnxByteArray::fromHex(ui->data->text().toLatin1()); + m_tunnel.sendTunnelingFeatureSet(feature, data); + populateFrame(QKnxNetIp::ServiceType::TunnelingFeatureSet, feature, data); +} + +void MainWindow::on_connection_clicked() +{ + if (ui->devices->count() <= 0) + return; + + if (m_tunnel.state() == QKnxNetIpTunnel::State::Connected) + return m_tunnel.disconnectFromHost(); + + m_tunnel.setLocalAddress(QHostAddress(ui->interfaces->currentData().toString())); + + m_last = new QTreeWidgetItem(ui->communication, m_last); + m_last->setText(0, tr("Establish connection to: %1 (%2 : %3)") + .arg(m_device->info().deviceName()) + .arg(m_device->info().controlEndpointAddress().toString()) + .arg(m_device->info().controlEndpointPort())); + m_last->setFirstColumnSpanned(true); + + if (ui->secureSession->isChecked()) { + auto secureConfiguration = m_secureConfigs.value(ui->secureConfigs->currentIndex()); + secureConfiguration.setKeepSecureSessionAlive(true); + m_tunnel.setSecureConfiguration(secureConfiguration); + m_tunnel.connectToHostEncrypted(m_device->info().controlEndpointAddress(), + m_device->info().controlEndpointPort()); + } else { + m_tunnel.connectToHost(m_device->info().controlEndpointAddress(), + m_device->info().controlEndpointPort(), QKnxNetIp::HostProtocol::UDP_IPv4); + } +} + +void MainWindow::setupInterfaces() +{ + auto firstItem = new QStandardItem(tr("Interface: --Select One--")); + qobject_cast<QStandardItemModel*>(ui->interfaces->model())->appendRow(firstItem); + firstItem->setSelectable(false); + + const auto interfaces = QNetworkInterface::allInterfaces(); + for (int i = 0; i < interfaces.size(); i++) { + const auto addressEntries = interfaces[i].addressEntries(); + for (int j = 0; j < addressEntries.size(); j++) { + const auto ip = addressEntries[j].ip(); + if (ip.isLoopback() || ip.toIPv4Address() == 0) + continue; + ui->interfaces->addItem(interfaces[i].name() + ": " + ip.toString(), ip.toString()); + } + } + ui->interfaces->setCurrentIndex(bool(ui->interfaces->count())); +} + +void MainWindow::toggleUi(bool value) +{ + ui->serviceTypes->setEnabled(value); + ui->interfaceFeatures->setEnabled(value); + const auto type = ui->serviceTypes->itemData(ui->serviceTypes->currentIndex()) + .value<QKnxNetIp::ServiceType>(); + ui->sendRead->setEnabled(value && type == QKnxNetIp::ServiceType::TunnelingFeatureGet); + ui->sendWrite->setEnabled(value && type == QKnxNetIp::ServiceType::TunnelingFeatureSet); + ui->data->setEnabled(value && type == QKnxNetIp::ServiceType::TunnelingFeatureSet); + ui->interfaces->setDisabled(value); + ui->devices->setDisabled(value && bool(ui->devices->count())); +} + +void MainWindow::populateServiceTypesComboBox() +{ + const auto typeEnum = QMetaEnum::fromType<QKnxNetIp::ServiceType>(); + ui->serviceTypes->addItem(tr("Tunneling Feature Get"), typeEnum.keyToValue("TunnelingFeatureGet")); + ui->serviceTypes->addItem(tr("Tunneling Feature Set"), typeEnum.keyToValue("TunnelingFeatureSet")); +} + +void MainWindow::populateInterfaceFeaturesComboBox() +{ + const auto typeEnum = QMetaEnum::fromType<QKnx::InterfaceFeature>(); + for (int i = 0; i < typeEnum.keyCount(); ++i) { + if (!QKnx::isInterfaceFeature(QKnx::InterfaceFeature(typeEnum.value(i)))) + continue; + ui->interfaceFeatures->addItem(typeEnum.key(i), typeEnum.value(i)); + } +} + +void MainWindow::setText(QKnx::InterfaceFeature feature, const QKnxByteArray &data) +{ + const auto hex = data.toByteArray().toHex(); + m_last->setText(5, QStringLiteral("0x") + QLatin1String(hex, hex.size())); + + QString value; + switch (feature) { + case QKnx::InterfaceFeature::ActiveEmiType: + case QKnx::InterfaceFeature::SupportedEmiType: { + QStringList types; + const auto emi = QKnx::EmiTypes(hex.toUShort(nullptr, 16)); + const auto metaEnum = QMetaEnum::fromType<QKnx::EmiType>(); + if (emi.testFlag(QKnx::EmiType::EMI1)) + types += QLatin1String(metaEnum.valueToKey(int(QKnx::EmiType::EMI1))); + if (emi.testFlag(QKnx::EmiType::EMI2)) + types += QLatin1String(metaEnum.valueToKey(int(QKnx::EmiType::EMI2))); + if (emi.testFlag(QKnx::EmiType::cEMI)) + types += QLatin1String(metaEnum.valueToKey(int(QKnx::EmiType::cEMI))); + value = types.join(QLatin1Char('|')); + } break; + + case QKnx::InterfaceFeature::HostDeviceDescriptorType0: + value = QString::number(hex.toUInt(nullptr, 16)); + break; + + case QKnx::InterfaceFeature::BusConnectionStatus: { + const auto metaEnum = QMetaEnum::fromType<QKnxState::State>(); + value = metaEnum.valueToKey(int(QKnxState::State(data.value(0)))); + } break; + + case QKnx::InterfaceFeature::MaximumApduLength: + value = QString::number(hex.toUShort(nullptr, 16)); + break; + + case QKnx::InterfaceFeature::KnxManufacturerCode: { + const auto id = hex.toUShort(nullptr, 16); + value = QKnx::Ets::Manufacturers::fromId(id, QString::number(id)); + } break; + + case QKnx::InterfaceFeature::IndividualAddress: + value = QKnxAddress(QKnxAddress::Type::Individual, data).toString(); + break; + + case QKnx::InterfaceFeature::InterfaceFeatureInfoServiceEnable: { + const auto metaEnum = QMetaEnum::fromType<QKnxEnable::State>(); + value = metaEnum.valueToKey(int(QKnxEnable::State(data.value(0)))); + } break; + + default: + break; + } + m_last->setText(3, value); + +} + +void MainWindow::populateFrame(QKnxNetIp::ServiceType type, QKnx::InterfaceFeature feature, + const QKnxByteArray &value, int returnCode) +{ + m_last = new QTreeWidgetItem(ui->communication, m_last); + + auto metaEnum = QMetaEnum::fromType<QKnxNetIp::ServiceType>(); + m_last->setText(1, metaEnum.valueToKey(int(type))); + + metaEnum = QMetaEnum::fromType<QKnx::InterfaceFeature>(); + m_last->setText(2, metaEnum.valueToKey(int(feature))); + + if (type != QKnxNetIp::ServiceType::TunnelingFeatureGet) + setText(feature, value); + + metaEnum = QMetaEnum::fromType<QKnx::ReturnCode>(); + if (const auto code = metaEnum.valueToKey(returnCode)) + m_last->setText(4, code); +} + +void MainWindow::onFeatureResponseReceived(QKnx::InterfaceFeature feature, QKnx::ReturnCode code, + const QKnxByteArray &value) +{ + populateFrame(QKnxNetIp::ServiceType::TunnelingFeatureResponse, feature, value, int(code)); +} + +void MainWindow::onFeatureInfoReceived(QKnx::InterfaceFeature feature, const QKnxByteArray &value) +{ + populateFrame(QKnxNetIp::ServiceType::TunnelingFeatureInfo, feature, value); +} diff --git a/examples/knx/feature/mainwindow.h b/examples/knx/feature/mainwindow.h new file mode 100644 index 0000000..42d827b --- /dev/null +++ b/examples/knx/feature/mainwindow.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2019 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$ +** +******************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QtKnx> +#include <QtWidgets> + +QT_BEGIN_NAMESPACE + +namespace Ui { + class MainWindow; +} + +QT_END_NAMESPACE + +class DeviceItem; +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private slots: + void onConnected(); + void onDisconnected(); + void onDeviceDiscovered(QKnxNetIpServerInfo info); + void onErrorOccurred(QKnxNetIpEndpointConnection::Error error, QString errorString); + + void on_actionImport_triggered(); + void on_devices_currentIndexChanged(int index); + void on_serviceTypes_currentIndexChanged(int index); + + void on_sendRead_clicked(); + void on_sendWrite_clicked(); + void on_connection_clicked(); + +private: + void setupInterfaces(); + void toggleUi(bool enable); + void populateServiceTypesComboBox(); + void populateInterfaceFeaturesComboBox(); + + void setText(QKnx::InterfaceFeature feature, const QKnxByteArray &value); + void populateFrame(QKnxNetIp::ServiceType type, QKnx::InterfaceFeature feature, + const QKnxByteArray &value, int returnCode = -1); + void onFeatureResponseReceived(QKnx::InterfaceFeature feature, QKnx::ReturnCode code, + const QKnxByteArray &value); + void onFeatureInfoReceived(QKnx::InterfaceFeature feature, const QKnxByteArray &value); + +private: + Ui::MainWindow *ui { nullptr }; + + DeviceItem *m_device { nullptr }; + QTreeWidgetItem *m_last { nullptr }; + QTreeWidgetItem *m_current { nullptr }; + + QKnxNetIpTunnel m_tunnel; + QKnxNetIpServerDiscoveryAgent m_discoveryAgent; + QVector<QKnxNetIpSecureConfiguration> m_secureConfigs; +}; + +#endif // MAINWINDOW_H diff --git a/examples/knx/feature/mainwindow.ui b/examples/knx/feature/mainwindow.ui new file mode 100644 index 0000000..ec92b4f --- /dev/null +++ b/examples/knx/feature/mainwindow.ui @@ -0,0 +1,332 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>MainWindow</class> + <widget class="QMainWindow" name="MainWindow"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>867</width> + <height>416</height> + </rect> + </property> + <property name="windowTitle"> + <string>KNX tunneling features example</string> + </property> + <widget class="QWidget" name="centralwidget"> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0,1,0,0"> + <item row="0" column="0"> + <widget class="QLabel" name="serviceTypesLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Service type:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QComboBox" name="serviceTypes"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="interfaceFeaturesLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Interface feature:</string> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QComboBox" name="interfaceFeatures"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item row="0" column="4"> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item row="0" column="5"> + <widget class="QPushButton" name="sendRead"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Read</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="dataLabel"> + <property name="text"> + <string>Data:</string> + </property> + </widget> + </item> + <item row="1" column="5"> + <widget class="QPushButton" name="sendWrite"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Write</string> + </property> + </widget> + </item> + <item row="1" column="1" colspan="3"> + <widget class="QLineEdit" name="data"> + <property name="enabled"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QTreeWidget" name="communication"> + <property name="contextMenuPolicy"> + <enum>Qt::DefaultContextMenu</enum> + </property> + <property name="uniformRowHeights"> + <bool>true</bool> + </property> + <attribute name="headerShowSortIndicator" stdset="0"> + <bool>false</bool> + </attribute> + <column> + <property name="text"> + <string>#</string> + </property> + </column> + <column> + <property name="text"> + <string>Service type</string> + </property> + </column> + <column> + <property name="text"> + <string>Interface feature</string> + </property> + </column> + <column> + <property name="text"> + <string>Value</string> + </property> + </column> + <column> + <property name="text"> + <string>Status code</string> + </property> + </column> + <column> + <property name="text"> + <string>Data</string> + </property> + </column> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,1,0,1,0,1,0,0"> + <item> + <widget class="QLabel" name="interfaceLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Interface:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="interfaces"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="deviceLabel"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Device:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="devices"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <widget class="QCheckBox" name="secureSession"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Use secure session</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="secureConfigs"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <spacer name="spacerRight"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::Expanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="connection"> + <property name="text"> + <string>Connect</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + <widget class="QMenuBar" name="menuBar"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>867</width> + <height>21</height> + </rect> + </property> + <widget class="QMenu" name="menuFile"> + <property name="title"> + <string>File</string> + </property> + <widget class="QMenu" name="menuImport"> + <property name="title"> + <string>Import</string> + </property> + <addaction name="actionImport"/> + </widget> + <addaction name="menuImport"/> + <addaction name="separator"/> + <addaction name="actionExit"/> + </widget> + <widget class="QMenu" name="menuEdit"> + <property name="title"> + <string>Edit</string> + </property> + <addaction name="actionClear"/> + </widget> + <addaction name="menuFile"/> + <addaction name="menuEdit"/> + </widget> + <action name="actionExit"> + <property name="text"> + <string>Exit</string> + </property> + </action> + <action name="actionImport"> + <property name="text"> + <string>KNX keyring file...</string> + </property> + </action> + <action name="actionClear"> + <property name="text"> + <string>Clear telegram list</string> + </property> + </action> + </widget> + <resources/> + <connections> + <connection> + <sender>secureSession</sender> + <signal>toggled(bool)</signal> + <receiver>secureConfigs</receiver> + <slot>setEnabled(bool)</slot> + <hints> + <hint type="sourcelabel"> + <x>516</x> + <y>391</y> + </hint> + <hint type="destinationlabel"> + <x>634</x> + <y>393</y> + </hint> + </hints> + </connection> + </connections> +</ui> diff --git a/examples/knx/knx.pro b/examples/knx/knx.pro index 2afb053..2a74801 100644 --- a/examples/knx/knx.pro +++ b/examples/knx/knx.pro @@ -5,5 +5,5 @@ TEMPLATE = subdirs SUBDIRS += discoverer knxproj tunnelclient router qtHaveModule(widgets) { - SUBDIRS += knxeditor group device + SUBDIRS += knxeditor group device feature } |