summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKarsten Heimrich <karsten.heimrich@qt.io>2019-04-15 12:04:27 +0200
committerKarsten Heimrich <karsten.heimrich@qt.io>2019-04-17 12:12:52 +0000
commit60d096199945d39aad92384e3ba9a47be406d650 (patch)
tree2f85c10fdd5415b614bc253d2c6a502a98227b39
parent44226e74b903c140be0953b797323ba1f19f4bef (diff)
Introduce local device management examplev5.13.0-beta3
Change-Id: I472d40f5941d4c90c07ffcc8ab61b85665c0d932 Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io> Reviewed-by: Karsten Heimrich <karsten.heimrich@qt.io>
-rw-r--r--examples/knx/device/device.pro18
-rw-r--r--examples/knx/device/deviceitem.cpp66
-rw-r--r--examples/knx/device/deviceitem.h70
-rw-r--r--examples/knx/device/main.cpp62
-rw-r--r--examples/knx/device/mainwindow.cpp375
-rw-r--r--examples/knx/device/mainwindow.h106
-rw-r--r--examples/knx/device/mainwindow.ui469
-rw-r--r--examples/knx/doc/device.qdoc165
-rw-r--r--examples/knx/doc/images/device.pngbin0 -> 23533 bytes
-rw-r--r--examples/knx/knx.pro2
-rw-r--r--src/knx/netip/qknxnetipendpointconnection.cpp18
11 files changed, 1344 insertions, 7 deletions
diff --git a/examples/knx/device/device.pro b/examples/knx/device/device.pro
new file mode 100644
index 0000000..c8510f1
--- /dev/null
+++ b/examples/knx/device/device.pro
@@ -0,0 +1,18 @@
+TEMPLATE = app
+TARGET = device
+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/device
+INSTALLS += target
diff --git a/examples/knx/device/deviceitem.cpp b/examples/knx/device/deviceitem.cpp
new file mode 100644
index 0000000..c353d1b
--- /dev/null
+++ b/examples/knx/device/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/device/deviceitem.h b/examples/knx/device/deviceitem.h
new file mode 100644
index 0000000..5f59407
--- /dev/null
+++ b/examples/knx/device/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/device/main.cpp b/examples/knx/device/main.cpp
new file mode 100644
index 0000000..e3e9a74
--- /dev/null
+++ b/examples/knx/device/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/device/mainwindow.cpp b/examples/knx/device/mainwindow.cpp
new file mode 100644
index 0000000..429b578
--- /dev/null
+++ b/examples/knx/device/mainwindow.cpp
@@ -0,0 +1,375 @@
+/****************************************************************************
+**
+** 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();
+
+ auto treeWidget = new QTreeWidget;
+ treeWidget->setHeaderHidden(true);
+ treeWidget->setUniformRowHeights(true);
+
+ ui->propertyId->setModel(treeWidget->model());
+ ui->propertyId->setView(treeWidget);
+ on_interfaceObjectTypes_currentTextChanged(QStringLiteral("General"));
+
+ populateInterfaceObjectTypesComboBox();
+
+ connect(ui->actionExit, &QAction::triggered, this, &QApplication::quit);
+ connect(ui->actionClear, &QAction::triggered, ui->communication, &QTreeWidget::clear);
+
+ connect(&m_management, &QKnxNetIpDeviceManagement::connected, this, &MainWindow::onConnected);
+ connect(&m_management, &QKnxNetIpDeviceManagement::disconnected, this, &MainWindow::onDisconnected);
+ connect(&m_management, &QKnxNetIpDeviceManagement::frameReceived, this, &MainWindow::populateFrame);
+ connect(&m_management, &QKnxNetIpDeviceManagement::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);
+ m_discoveryAgent.setDiscoveryMode(QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV1
+ | QKnxNetIpServerDiscoveryAgent::DiscoveryMode::CoreV2);
+ connect(&m_discoveryAgent, &QKnxNetIpServerDiscoveryAgent::deviceDiscovered,
+ this, &MainWindow::onDeviceDiscovered);
+
+ 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_management.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);
+ 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_interfaceObjectTypes_currentTextChanged(const QString &type)
+{
+ auto treeWidget = qobject_cast<QTreeWidget *>(ui->propertyId->view());
+ if (!treeWidget)
+ return;
+
+ // keep the 'General' property top level item
+ auto topLevelItem = treeWidget->takeTopLevelItem(0);
+ ui->propertyId->clear();
+ treeWidget->addTopLevelItem(topLevelItem);
+
+ int index = QKnxInterfaceObjectProperty::staticMetaObject.indexOfEnumerator(type.toLatin1());
+ if (index >= 0) {
+ const auto typeEnum = QKnxInterfaceObjectProperty::staticMetaObject.enumerator(index);
+ topLevelItem = new QTreeWidgetItem(treeWidget, { typeEnum.name() });
+ for (auto a = 0; a < typeEnum.keyCount(); ++a) {
+ auto item = new QTreeWidgetItem({ typeEnum.key(a) });
+ item->setData(0, Qt::UserRole, typeEnum.value(a));
+ topLevelItem->addChild(item);
+ }
+ topLevelItem->setFlags(topLevelItem->flags() &~Qt::ItemIsSelectable);
+ }
+
+ // select first sub item
+ treeWidget->expandItem(topLevelItem);
+ treeWidget->setCurrentItem(topLevelItem, 0);
+ ui->propertyId->setRootModelIndex(treeWidget->currentIndex());
+ ui->propertyId->setCurrentIndex(0);
+ treeWidget->setCurrentItem(treeWidget->invisibleRootItem(), 0);
+ ui->propertyId->setRootModelIndex(treeWidget->currentIndex());
+}
+
+void MainWindow::on_sendRead_clicked()
+{
+ const auto frame = QKnxDeviceManagementFrame::propertyReadBuilder()
+ .setObjectType(ui->interfaceObjectTypes->currentData().toInt())
+ .setObjectInstance(quint8(ui->objectInstance->value()))
+ .setProperty(quint16(ui->propertyId->currentData().toUInt()))
+ .setNumberOfElements(quint8(ui->numberOfElements->value()))
+ .setStartIndex(quint16(ui->startIndex->value()))
+ .createRequest();
+
+ populateFrame(frame);
+ m_management.sendFrame(frame);
+}
+
+void MainWindow::on_sendWrite_clicked()
+{
+ const auto frame = QKnxDeviceManagementFrame::propertyWriteBuilder()
+ .setObjectType(ui->interfaceObjectTypes->currentData().toInt())
+ .setObjectInstance(quint8(ui->objectInstance->value()))
+ .setProperty(quint16(ui->propertyId->currentData().toUInt()))
+ .setNumberOfElements(quint8(ui->numberOfElements->value()))
+ .setStartIndex(quint16(ui->startIndex->value()))
+ .createRequest(QKnxByteArray::fromHex(ui->data->text().toLatin1()));
+
+ populateFrame(frame);
+ m_management.sendFrame(frame);
+}
+
+void MainWindow::on_connection_clicked()
+{
+ if (ui->devices->count() <= 0)
+ return;
+
+ if (m_management.state() == QKnxNetIpTunnel::State::Connected)
+ return m_management.disconnectFromHost();
+
+ m_management.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_management.setSecureConfiguration(secureConfiguration);
+ m_management.connectToHostEncrypted(m_device->info().controlEndpointAddress(),
+ m_device->info().controlEndpointPort());
+ } else {
+ m_management.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->interfaceObjectTypes->setEnabled(value);
+ ui->objectInstance->setEnabled(value);
+ ui->propertyId->setEnabled(value);
+ ui->numberOfElements->setEnabled(value);
+ ui->startIndex->setEnabled(value);
+ ui->sendRead->setEnabled(value);
+ ui->data->setEnabled(value);
+ ui->sendWrite->setEnabled(value);
+ ui->interfaces->setDisabled(value);
+ ui->devices->setDisabled(value && bool(ui->devices->count()));
+}
+
+void MainWindow::populateInterfaceObjectTypesComboBox()
+{
+ const auto typeEnum = QMetaEnum::fromType<QKnxInterfaceObjectType::System>();
+ for (int i = 0; i < typeEnum.keyCount(); ++i)
+ ui->interfaceObjectTypes->addItem(typeEnum.key(i), typeEnum.value(i));
+}
+
+void MainWindow::populateFrame(const QKnxDeviceManagementFrame &frame)
+{
+ m_last = new QTreeWidgetItem(ui->communication, m_last);
+
+ if (frame.isNegativeConfirmation()) {
+ const auto metaEnum = QMetaEnum::fromType<QKnxNetIpCemiServer::Error>();
+ m_last->setText(0, tr("Received negative confirmation: Error (%1)")
+ .arg(metaEnum.valueToKey(int(frame.error()))));
+ return m_last->setFirstColumnSpanned(true);
+ }
+
+ auto metaEnum = QMetaEnum::fromType<QKnxDeviceManagementFrame::MessageCode>();
+ m_last->setText(1, metaEnum.valueToKey(int(frame.messageCode())));
+
+ metaEnum = QMetaEnum::fromType<QKnxInterfaceObjectType::System>();
+ const auto type = metaEnum.valueToKey(int(frame.objectType()));
+ m_last->setText(2, QString::fromLatin1(type));
+
+ m_last->setText(3, QString::number(frame.objectInstance()));
+
+ int index = QKnxInterfaceObjectProperty::staticMetaObject.indexOfEnumerator(type);
+ if (index >= 0) {
+ metaEnum = QKnxInterfaceObjectProperty::staticMetaObject.enumerator(index);
+ m_last->setText(4, metaEnum.valueToKey(int(frame.property())));
+ } else {
+ m_last->setText(4, QString::number(int(frame.property())));
+ }
+
+ m_last->setText(5, QString::number(frame.numberOfElements()));
+ m_last->setText(6, QString::number(frame.startIndex()));
+
+ auto bytes = frame.data().toHex();
+ m_last->setText(7, QString::fromLatin1(bytes.toByteArray(), bytes.size()));
+ bytes = frame.bytes().toHex();
+ m_last->setText(8, QString::fromLatin1(bytes.toByteArray(), bytes.size()));
+
+ if (m_current) {
+ const auto region = ui->communication->viewport()->visibleRegion();
+ const auto currentRect = ui->communication->visualItemRect(m_current);
+ if (region.contains({ currentRect.left(), currentRect.top() - currentRect.height() })
+ && region.contains(ui->communication->visualItemRect(m_last))) {
+ ui->communication->scrollToItem(m_last);
+ }
+ } else {
+ ui->communication->scrollToItem(m_last);
+ }
+}
diff --git a/examples/knx/device/mainwindow.h b/examples/knx/device/mainwindow.h
new file mode 100644
index 0000000..3676cc9
--- /dev/null
+++ b/examples/knx/device/mainwindow.h
@@ -0,0 +1,106 @@
+/****************************************************************************
+**
+** 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_interfaceObjectTypes_currentTextChanged(const QString &type);
+
+ void on_sendRead_clicked();
+ void on_sendWrite_clicked();
+ void on_connection_clicked();
+
+private:
+ void setupInterfaces();
+ void toggleUi(bool enable);
+ void populateInterfaceObjectTypesComboBox();
+ void populateFrame(const QKnxDeviceManagementFrame &frame);
+
+private:
+ Ui::MainWindow *ui { nullptr };
+
+ DeviceItem *m_device { nullptr };
+ QTreeWidgetItem *m_last { nullptr };
+ QTreeWidgetItem *m_current { nullptr };
+
+ QKnxNetIpDeviceManagement m_management;
+ QKnxNetIpServerDiscoveryAgent m_discoveryAgent;
+ QVector<QKnxNetIpSecureConfiguration> m_secureConfigs;
+};
+
+#endif // MAINWINDOW_H
diff --git a/examples/knx/device/mainwindow.ui b/examples/knx/device/mainwindow.ui
new file mode 100644
index 0000000..6e5791b
--- /dev/null
+++ b/examples/knx/device/mainwindow.ui
@@ -0,0 +1,469 @@
+<?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 local device management example</string>
+ </property>
+ <widget class="QWidget" name="centralwidget">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QGridLayout" name="gridLayout" columnstretch="0,1,0,0,0,1,0,0">
+ <item row="0" column="0">
+ <widget class="QLabel" name="interfaceObjectTypeLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Interface object type:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="interfaceObjectTypes">
+ <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="objectInstanceLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Object instance:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="3">
+ <widget class="QSpinBox" name="objectInstance">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>255</number>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="4">
+ <widget class="QLabel" name="propertyIdLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Property ID:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="5">
+ <widget class="QComboBox" name="propertyId">
+ <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="6">
+ <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="7">
+ <widget class="QPushButton" name="sendRead">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Read</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="numberOfElementsLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Number of elements:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QSpinBox" name="numberOfElements">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>15</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="startIndexLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Start index:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QSpinBox" name="startIndex">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximum">
+ <number>4095</number>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item row="1" column="2">
+ <widget class="QLabel" name="dataLabel">
+ <property name="text">
+ <string>Data:</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="3" colspan="3">
+ <widget class="QLineEdit" name="data">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="7">
+ <widget class="QPushButton" name="sendWrite">
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Write</string>
+ </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>Message code</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Interface object type</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Object instance</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Property ID</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Number of elements</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Start index</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Data</string>
+ </property>
+ </column>
+ <column>
+ <property name="text">
+ <string>Frame</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/doc/device.qdoc b/examples/knx/doc/device.qdoc
new file mode 100644
index 0000000..33cd8e6
--- /dev/null
+++ b/examples/knx/doc/device.qdoc
@@ -0,0 +1,165 @@
+/****************************************************************************
+**
+** 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 device
+ \title KNX Local Device Management Example
+ \ingroup qtknx-examples
+
+ \brief A KNX client for handling KNXnet/IP local device management.
+
+ \image device.png "KNX local device management example"
+
+ The KNX local device management example user interface contains
+ various Qt Widgets, most prominently a \l QTreeWidget to display
+ detailed information about sent and received KNX property read,
+ write, and confirmation 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 KNXnet/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 issue a property read or write command to an existing
+ \c {Interface Object} in the management server. The example currently
+ limits the possible read or write operations that can be used to
+ \c {M_PropRead.req} and \c {m_PropWrite.req} 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 device/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 QKnxNetIpDeviceManagement used to
+ establish the connection to the KNXnet/Ip device and a list of imported
+ KNX \l QKnxNetIpSecureConfiguration secure configurations.
+
+ There are signal handlers installed for every signal emitted by the
+ \l QKnxNetIpDeviceManagement. 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
+ device management connection and connect to the KNXnet/IP device:
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto MainWindow::MainWindow
+ \printuntil {
+ \dots
+ \skipto QKnxNetIpDeviceManagement::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 device management
+ connections have been made. Here is the code snippet doing it:
+
+ \quotefromfile device/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 management frames. The
+ \c MainWindow::on_devices_currentIndexChanged method saves the selected
+ KNXnet/IP device in the the \c MainWindow instance.
+
+ To populate the \uicontrol {Interface object type} drop-down box with
+ available KNX interface object types, the function
+ \c MainWindow::populateInterfaceObjectTypesComboBox is invoked. When the
+ application user switches the currently selected object type to a different
+ one, the \uicontrol {Property ID} gets updated with matching property IDs
+ for the active interface object:
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto MainWindow::on_interfaceObjectTypes_currentTextChanged
+ \printuntil {
+ \dots
+ \skipto QKnxInterfaceObjectProperty::staticMetaObject.indexOfEnumerator
+ \printuntil topLevelItem->setFlags
+ \skipto /^ {4}\}/
+ \printuntil /^ {4}\}/
+ \dots
+ \skipto /^\}/
+ \printuntil /^\}/
+
+ In this last example, when the user triggers the \uicontrol {Read} button,
+ the function \c MainWindow::on_sendRead_clicked() is called and a frame is
+ sent to the KNX network to trigger a property read from the given interface
+ object type and for selected property ID:
+
+ \quotefromfile device/mainwindow.cpp
+ \skipto MainWindow::on_sendRead_clicked
+ \printuntil /^\}/
+
+ \section1 The Main Function
+
+ The KNX local device management example \c main() function does not have
+ any special handling. It looks like the main function for any Qt app:
+
+ \quotefromfile group/main.cpp
+ \skipto #include
+ \printuntil /^\}/
+*/
diff --git a/examples/knx/doc/images/device.png b/examples/knx/doc/images/device.png
new file mode 100644
index 0000000..7df2d17
--- /dev/null
+++ b/examples/knx/doc/images/device.png
Binary files differ
diff --git a/examples/knx/knx.pro b/examples/knx/knx.pro
index 63cf3fd..2afb053 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
+ SUBDIRS += knxeditor group device
}
diff --git a/src/knx/netip/qknxnetipendpointconnection.cpp b/src/knx/netip/qknxnetipendpointconnection.cpp
index 03785e1..51786ee 100644
--- a/src/knx/netip/qknxnetipendpointconnection.cpp
+++ b/src/knx/netip/qknxnetipendpointconnection.cpp
@@ -642,15 +642,21 @@ void QKnxNetIpEndpointConnectionPrivate::cleanup()
bool QKnxNetIpEndpointConnectionPrivate::sendCemiRequest()
{
- if (!m_waitForAcknowledgement && !m_tcpSocket) {
+ if (m_udpSocket) {
+ if (m_waitForAcknowledgement)
+ return false; // still waiting for an ACK from an previous request
+
m_waitForAcknowledgement = true;
m_udpSocket->writeDatagram(m_lastSendCemiRequest.bytes().toByteArray(),
- m_remoteDataEndpoint.address,
- m_remoteDataEndpoint.port);
+ m_remoteDataEndpoint.address,
+ m_remoteDataEndpoint.port);
m_cemiRequests++;
m_acknowledgeTimer->start(m_acknowledgeTimeout);
- } else if (m_tcpSocket) {
+ return true;
+ }
+
+ if (m_tcpSocket) {
if (m_secureConfig.isValid()) {
auto secureFrame = QKnxNetIpSecureWrapperProxy::secureBuilder()
.setSecureSessionId(m_sessionId)
@@ -664,9 +670,9 @@ bool QKnxNetIpEndpointConnectionPrivate::sendCemiRequest()
} else {
m_tcpSocket->write(m_lastSendCemiRequest.bytes().toByteArray());
}
- m_waitForAcknowledgement = false;
+ return true; // TCP connections do not send an ACK
}
- return !m_waitForAcknowledgement;
+ return false;
}
void QKnxNetIpEndpointConnectionPrivate::sendStateRequest()