KNX Editor Example
/****************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtKnx module. ** ** $QT_BEGIN_LICENSE:GPL$ ** 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 General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ******************************************************************************/#include "tunnelingfeatures.h" #include "ui_tunnelingfeatures.h" #include <QtCore/QMetaEnum> #include <QtCore/QMetaType> #include <QtKnx/QKnx1Bit> #include <QtKnx/QKnx> #include <QtKnx/QKnxUtils> bool validFeature(const QKnx::NetIp::ServiceType &frameType, const QKnx::InterfaceFeature &feature, const QKnxByteArray &value) { using ServType = QKnx::NetIp::ServiceType; if (frameType != ServType::TunnelingFeatureSet) return true; using FeatureType = QKnx::InterfaceFeature; switch (feature) { case FeatureType::SupportedEmiType: case FeatureType::HostDeviceDescriptorType0: case FeatureType::KnxManufacturerCode: case FeatureType::IndividualAddress: case FeatureType::MaximumApduLength: return value.size() == 2; case FeatureType::BusConnectionStatus: case FeatureType::InterfaceFeatureInfoServiceEnable: return value.size() == 1 && ((value.at(0) == 0x01) || (value.at(0) == 0x00)); case FeatureType::ActiveEmiType: return value.size() == 1; default: break; } return false; } TunnelingFeatures::TunnelingFeatures(QWidget *parent) : QWidget(parent) , ui(new Ui::TunnelingFeatures) { ui->setupUi(this); ui->tunnelServiceType->setEnabled(false); ui->featureIdentifier->setEnabled(false); ui->featureValue->setEnabled(false); connect(ui->connectTunneling, &QPushButton::clicked, this, [&]() { ui->textOuputTunneling->append(tr("Connecting to: %1 on port: %2 protocol: %3") .arg(m_server.controlEndpointAddress().toString()) .arg(m_server.controlEndpointPort()).arg(int(m_protocol))); m_tunnel.setLocalPort(0); if (ui->secureSessionCheckBox->isChecked()) { auto config = m_configs.value(ui->secureSessionCb->currentIndex()); config.setKeepSecureSessionAlive(true); m_tunnel.setSecureConfiguration(config); m_tunnel.connectToHostEncrypted(m_server.controlEndpointAddress(), m_server.controlEndpointPort()); } else { m_tunnel.connectToHost(m_server.controlEndpointAddress(), m_server.controlEndpointPort(), m_protocol); } }); connect(ui->tunnelingSend, &QPushButton::clicked, this, [&]() { using ServType = QKnx::NetIp::ServiceType; ServType type = ServType(quint16(ServType::TunnelingFeatureGet)); if (ui->tunnelServiceType->currentIndex() == 1) type = ServType(quint16(ServType::TunnelingFeatureSet)); using FeatureType = QKnx::InterfaceFeature; FeatureType featureType = FeatureType(quint8(FeatureType::SupportedEmiType) + ui->featureIdentifier->currentIndex()); QKnxByteArray bytes = QKnxByteArray::fromHex(ui->featureValue->text().toUtf8()); QKnxNetIpFrame frame; if (type == ServType::TunnelingFeatureGet) m_tunnel.sendTunnelingFeatureGet(featureType); else if (type == ServType::TunnelingFeatureSet) m_tunnel.sendTunnelingFeatureSet(featureType, bytes); ui->textOuputTunneling->append(tr("Status: (%1) Messages sent.").arg(m_tunnel .sequenceCount(QKnxNetIpEndpointConnection::SequenceType::Send) + 1)); }); connect(&m_tunnel, &QKnxNetIpTunnel::tunnelingFeatureInfoReceived, this, [&](QKnx::InterfaceFeature feature, const QKnxByteArray &value) { auto metaEnum = QMetaEnum::fromType<QKnx::InterfaceFeature>(); if (feature == QKnx::InterfaceFeature::InterfaceFeatureInfoServiceEnable) { auto state = QKnxSwitch::State(value.at(0)); auto metaEnumState = QMetaEnum::fromType<QKnxSwitch::State>(); ui->textOuputTunneling->append(tr("Received Tunneling Feature Info: Feature (%1), " "Value (%2)") .arg(QString::fromLatin1(metaEnum.valueToKey(int(feature)))) .arg(QString::fromLatin1(metaEnumState.valueToKey(int(state))))); } else { tr("Received Tunneling Feature Info: Feature (%1), Value: ") .arg(metaEnum.valueToKey(int(feature))) + QLatin1String(value.toByteArray(), value.size()); } }); connect(&m_tunnel, &QKnxNetIpTunnel::tunnelingFeatureResponseReceived, this, [&](QKnx::InterfaceFeature feature, QKnx::ReturnCode code, const QKnxByteArray &value) { auto metaEnum = QMetaEnum::fromType<QKnx::InterfaceFeature>(); auto metaReturnCode = QMetaEnum::fromType<QKnx::ReturnCode>(); if (feature == QKnx::InterfaceFeature::InterfaceFeatureInfoServiceEnable || feature == QKnx::InterfaceFeature::BusConnectionStatus) { auto state = QKnxSwitch::State(value.at(0)); auto metaEnumState = QMetaEnum::fromType<QKnxSwitch::State>(); ui->textOuputTunneling->append(tr("Received Tunneling Feature Response: Feature (%1), " "Return Code (%2), Value (%3)") .arg(QString::fromLatin1(metaEnum.valueToKey(int(feature)))) .arg(QString::fromLatin1(metaReturnCode.valueToKey(int(code)))) .arg(QString::fromLatin1(metaEnumState.valueToKey(int(state))))); } else if (feature == QKnx::InterfaceFeature::IndividualAddress) { ui->textOuputTunneling->append(tr("Received Tunneling Feature Response: Feature (%1), " "Return Code (%2), Individual Address Value (%3)") .arg(QString::fromLatin1(metaEnum.valueToKey(int(feature)))) .arg(QString::fromLatin1(metaReturnCode.valueToKey(int(code)))) .arg(QKnxAddress(QKnxAddress::Type::Individual, value).toString())); } else if (feature == QKnx::InterfaceFeature::KnxManufacturerCode || feature == QKnx::InterfaceFeature::MaximumApduLength) { ui->textOuputTunneling->append(tr("Received Tunneling Feature Response: Feature (%1), " "Return Code (%2), Value (%3)") .arg(QString::fromLatin1(metaEnum.valueToKey(int(feature)))) .arg(QString::fromLatin1(metaReturnCode.valueToKey(int(code)))) .arg(QKnxUtils::QUint16::fromBytes(value))); } else if (feature == QKnx::InterfaceFeature::ActiveEmiType || feature == QKnx::InterfaceFeature::SupportedEmiType) { QString str; auto types = QKnx::EmiTypes(value.at(0)); auto metaEmiType = QMetaEnum::fromType<QKnx::EmiType>(); if (types.testFlag(QKnx::EmiType::EMI1)) str = QLatin1String(metaEmiType.valueToKey(int(QKnx::EmiType::EMI1))); if (types.testFlag(QKnx::EmiType::EMI2)) str += "|" + QLatin1String(metaEmiType.valueToKey(int(QKnx::EmiType::EMI2))); if (types.testFlag(QKnx::EmiType::cEMI)) str += "|" + QLatin1String(metaEmiType.valueToKey(int(QKnx::EmiType::cEMI))); ui->textOuputTunneling->append(tr("Received Tunneling Feature Response: Feature (%1), " "Return Code (%2), EMI (%3)") .arg(QString::fromLatin1(metaEnum.valueToKey(int(feature)))) .arg(QString::fromLatin1(metaReturnCode.valueToKey(int(code)))) .arg(str.isEmpty() ? QLatin1String(metaEmiType.valueToKey(int(QKnx::EmiType::Unknown))) : str)); } else { ui->textOuputTunneling->append(tr("Received Tunneling Feature Response: Feature (%1), " "Return Code (%2), Value: ") .arg(metaEnum.valueToKey(int(feature))) .arg(QString::fromLatin1(metaReturnCode.valueToKey(int(code)))) + QLatin1String(value.toByteArray(), value.size())); } }); connect(&m_tunnel, &QKnxNetIpTunnel::connected, this, [&] { ui->connectTunneling->setEnabled(false); ui->disconnectTunneling->setEnabled(true); ui->tunnelServiceType->setEnabled(true); ui->featureIdentifier->setEnabled(true); if (ui->tunnelServiceType->currentText() == "TunnelingFeatureSet") { ui->featureValue->setEnabled(true); ui->tunnelingSend->setEnabled(!ui->featureValue->text().isEmpty()); } else { ui->featureValue->setEnabled(false); ui->tunnelingSend->setEnabled(true); } ui->textOuputTunneling->append(tr("Successfully connected to: %1 on port: %2").arg(m_server .controlEndpointAddress().toString()).arg(m_server.controlEndpointPort())); ui->textOuputTunneling->append("Status: Connected."); }); connect(ui->disconnectTunneling, &QPushButton::clicked, this, [&]() { m_tunnel.disconnectFromHost(); }); connect(&m_tunnel, &QKnxNetIpTunnel::disconnected, this, [&] { ui->connectTunneling->setEnabled(true); ui->disconnectTunneling->setEnabled(false); ui->tunnelingSend->setEnabled(false); ui->tunnelServiceType->setEnabled(false); ui->featureIdentifier->setEnabled(false); ui->featureValue->setEnabled(false); ui->textOuputTunneling->append(tr("Successfully disconnected from: %1 on port: %2\n") .arg(m_server.controlEndpointAddress().toString()).arg(m_server.controlEndpointPort())); ui->textOuputTunneling->append("Status: Disconnected."); }); connect(&m_tunnel, &QKnxNetIpTunnel::errorOccurred, this, [&] (QKnxNetIpEndpointConnection::Error, QString errorString) { ui->textOuputTunneling->append(errorString); }); connect(ui->tunnelServiceType, &QComboBox::currentTextChanged, this, [&](const QString &text) { if (text == QString("TunnelingFeatureSet")) { ui->featureValue->setEnabled(true); ui->textOuputTunneling->append("Status: Fill in the feature type and value fields."); } else { ui->featureValue->setEnabled(false); ui->textOuputTunneling->append(""); } checkFeatureValue(); }); connect(ui->featureValue, &QLineEdit::textChanged, this, [&](const QString &) { checkFeatureValue(); }); connect(ui->featureIdentifier, &QComboBox::currentTextChanged, this, [&](const QString &) { checkFeatureValue(); }); } TunnelingFeatures::~TunnelingFeatures() { delete ui; } void TunnelingFeatures::setNatAware(bool isNatAware) { m_tunnel.setNatAware(isNatAware); } void TunnelingFeatures::setLocalAddress(const QHostAddress &address) { m_tunnel.disconnectFromHost(); m_tunnel.setLocalAddress(address); } void TunnelingFeatures::setKnxNetIpServer(const QKnxNetIpServerInfo &server) { m_tunnel.disconnectFromHost(); m_server = server; if (m_tunnel.state() == QKnxNetIpEndpointConnection::State::Disconnected) { ui->connectTunneling->setEnabled(true); ui->disconnectTunneling->setEnabled(false); } updateSecureConfigCombo(); ui->tunnelingSend->setEnabled(false); ui->textOuputTunneling->append("Status: Start by clicking connect."); } void TunnelingFeatures::setTcpEnable(bool value) { m_protocol = (value ? QKnxNetIp::HostProtocol::TCP_IPv4 : QKnxNetIp::HostProtocol::UDP_IPv4); } void TunnelingFeatures::onKeyringChanged(const QVector<QKnxNetIpSecureConfiguration> &configs) { m_configs = configs; updateSecureConfigCombo(); } void TunnelingFeatures::checkFeatureValue() { if (!ui->featureValue->isEnabled()) { ui->textOuputTunneling->append(""); ui->tunnelingSend->setEnabled(m_tunnel.state() == QKnxNetIpEndpointConnection::State::Connected); return; } using ServType = QKnx::NetIp::ServiceType; ServType type = ServType(quint16(ServType::TunnelingFeatureGet)); if (ui->tunnelServiceType->currentIndex() == 1) type = ServType(quint16(ServType::TunnelingFeatureSet)); using FeatureType = QKnx::InterfaceFeature; FeatureType featureType = FeatureType(quint8(FeatureType::SupportedEmiType) + ui->featureIdentifier->currentIndex()); QKnxByteArray bytes = QKnxByteArray::fromHex(ui->featureValue->text().toUtf8()); auto text = ui->featureValue->text(); if (text.isEmpty() || !validFeature(type, featureType, bytes) || ((text.size() % 2) != 0)) { ui->textOuputTunneling->append("Status: Invalid value entered"); ui->tunnelingSend->setEnabled(false); return; } ui->textOuputTunneling->append("Status: Valid value entered, click send."); ui->tunnelingSend->setEnabled(m_tunnel.state() == QKnxNetIpEndpointConnection::State::Connected); } void TunnelingFeatures::updateSecureConfigCombo() { ui->secureSessionCb->clear(); ui->secureSessionCheckBox->setEnabled(!m_configs.isEmpty()); ui->secureSessionCheckBox->setChecked(m_protocol == QKnxNetIp::HostProtocol::TCP_IPv4); for (int i = 0; i < m_configs.size(); ++i) { const auto &config = m_configs[i]; if (m_server.individualAddress() != config.host()) continue; const auto ia = config.individualAddress(); ui->secureSessionCb->addItem(tr("User ID: %1 (Individual Address: %2)") .arg(config.userId()) .arg(ia.isValid() ? ia.toString() : tr("No specific address")), i); } }