diff options
author | Karsten Heimrich <karsten.heimrich@qt.io> | 2019-04-15 12:04:27 +0200 |
---|---|---|
committer | Karsten Heimrich <karsten.heimrich@qt.io> | 2019-04-17 12:12:52 +0000 |
commit | 60d096199945d39aad92384e3ba9a47be406d650 (patch) | |
tree | 2f85c10fdd5415b614bc253d2c6a502a98227b39 | |
parent | 44226e74b903c140be0953b797323ba1f19f4bef (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.pro | 18 | ||||
-rw-r--r-- | examples/knx/device/deviceitem.cpp | 66 | ||||
-rw-r--r-- | examples/knx/device/deviceitem.h | 70 | ||||
-rw-r--r-- | examples/knx/device/main.cpp | 62 | ||||
-rw-r--r-- | examples/knx/device/mainwindow.cpp | 375 | ||||
-rw-r--r-- | examples/knx/device/mainwindow.h | 106 | ||||
-rw-r--r-- | examples/knx/device/mainwindow.ui | 469 | ||||
-rw-r--r-- | examples/knx/doc/device.qdoc | 165 | ||||
-rw-r--r-- | examples/knx/doc/images/device.png | bin | 0 -> 23533 bytes | |||
-rw-r--r-- | examples/knx/knx.pro | 2 | ||||
-rw-r--r-- | src/knx/netip/qknxnetipendpointconnection.cpp | 18 |
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 Binary files differnew file mode 100644 index 0000000..7df2d17 --- /dev/null +++ b/examples/knx/doc/images/device.png 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() |