/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtKnx module.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "localdevicemanagement.h"#include "ui_localdevicemanagement.h"#include <QKnxByteArray>#include <QKnxDeviceManagementFrameBuilder>#include <QKnxInterfaceObjectPropertyDataType>#include <QKnxNetIpSecureConfiguration>#include <QMetaEnum>#include <QMetaType>#include <QTreeWidget>
LocalDeviceManagement::LocalDeviceManagement(QWidget* parent)
: QWidget(parent)
, ui(new Ui::LocalDeviceManagement)
{
ui->setupUi(this);
setupMessageCodeComboBox();
constauto setTreeWidget =[](QComboBox*comboBox) {
auto treeWidget =newQTreeWidget;
treeWidget->setHeaderHidden(true);
treeWidget->setUniformRowHeights(true);
comboBox->setModel(treeWidget->model());
comboBox->setView(treeWidget);
};
setTreeWidget(ui->property);
setTreeWidget(ui->objectType);
setupComboBox(ui->property,QKnxInterfaceObjectProperty::staticMetaObject);
setupComboBox(ui->objectType,QKnxInterfaceObjectType::staticMetaObject);
connect(ui->connectRequestDeviceManagement,&QPushButton::clicked,this,[&]() {
m_management.setLocalPort(0);
if (ui->secureSessionCheckBox->isChecked()) {
auto config = m_configs.value(ui->secureSessionCb->currentIndex());
config.setKeepSecureSessionAlive(true);
m_management.setSecureConfiguration(config);
m_management.connectToHostEncrypted(m_server.controlEndpointAddress(),
m_server.controlEndpointPort());
} else {
m_management.connectToHost(m_server.controlEndpointAddress(),
m_server.controlEndpointPort(), m_proto);
}
});
connect(&m_management,&QKnxNetIpDeviceManagement::connected,this,[&] {
ui->deviceManagementSendRequest->setEnabled(true);
ui->connectRequestDeviceManagement->setEnabled(false);
ui->disconnectRequestDeviceManagement->setEnabled(true);
ui->textOuputDeviceManagement->append(tr("Successfully connected to: %1 on port: %2")
.arg(m_server.controlEndpointAddress().toString()).arg(m_server.controlEndpointPort()));
m_management.sendFrame(QKnxDeviceManagementFrame::propertyReadBuilder()
.setObjectType(QKnxInterfaceObjectType::System::Device)
.setObjectInstance(1)
.setProperty(QKnxInterfaceObjectProperty::Device::IoList)
.setNumberOfElements(1)
.setStartIndex(0).createRequest());
});
connect(ui->disconnectRequestDeviceManagement,&QPushButton::clicked,this,[&]() {
m_management.disconnectFromHost();
});
connect(&m_management,&QKnxNetIpDeviceManagement::disconnected,this,[&] {
if (!ui)
return;
m_awaitIoListResponse =true;
ui->deviceManagementSendRequest->setEnabled(false);
ui->connectRequestDeviceManagement->setEnabled(true);
ui->disconnectRequestDeviceManagement->setEnabled(false);
setupComboBox(ui->objectType,QKnxInterfaceObjectType::staticMetaObject);
ui->textOuputDeviceManagement->append(tr("Successfully disconnected from: %1 on port: %2\n")
.arg(m_server.controlEndpointAddress().toString()).arg(m_server.controlEndpointPort()));
});
connect(ui->deviceManagementSendRequest,&QPushButton::clicked,this,[&]() {
ui->textOuputDeviceManagement->append(tr("Send device management frame with cEMI payload: ")
+ ui->cemiFrame->text());
auto data =QKnxByteArray::fromHex(ui->cemiFrame->text().toUtf8());
if (ui->cemiData->isEnabled())
data.append(QKnxByteArray::fromHex(ui->cemiData->text().toUtf8()));
m_management.sendFrame(QKnxDeviceManagementFrame::fromBytes(data,0,
data.size()));
});
connect(&m_management,&QKnxNetIpDeviceManagement::frameReceived,this,[&](QKnxDeviceManagementFrame frame) {
ui->textOuputDeviceManagement->append(tr("Received device management frame with cEMI ""payload: "+ frame.bytes().toHex().toByteArray()));
if (m_awaitIoListResponse)
handleIoListResponse(frame);
});
connect(&m_management,&QKnxNetIpDeviceManagement::errorOccurred,this,[&] (QKnxNetIpEndpointConnection::Error,QString errorString) {
ui->textOuputDeviceManagement->append(errorString);
});
ui->cemiData->setValidator(newQRegExpValidator(QRegExp("[0-9a-fA-F]+")));
ui->cemiFrame->setValidator(newQRegExpValidator(QRegExp("[0-9a-fA-F]+")));
}
LocalDeviceManagement::~LocalDeviceManagement()
{
delete ui;
ui = nullptr;
}
void LocalDeviceManagement::setNatAware(bool isNatAware)
{
m_management.setNatAware(isNatAware);
}
void LocalDeviceManagement::setLocalAddress(constQHostAddress&address)
{
m_management.disconnectFromHost();
m_management.setLocalAddress(address);
}
void LocalDeviceManagement::setKnxNetIpServer(constQKnxNetIpServerInfo&server)
{
m_management.disconnectFromHost();
m_server = server;
if (m_management.state() ==QKnxNetIpEndpointConnection::State::Disconnected) {
ui->connectRequestDeviceManagement->setEnabled(true);
ui->disconnectRequestDeviceManagement->setEnabled(false);
}
updateSecureConfigCombo();
ui->deviceManagementSendRequest->setEnabled(false);
}
void LocalDeviceManagement::setTcpEnable(bool value)
{
m_proto = (value ?QKnxNetIp::HostProtocol::TCP_IPv4 : QKnxNetIp::HostProtocol::UDP_IPv4);
}
void LocalDeviceManagement::clearLogging()
{
ui->textOuputDeviceManagement->clear();
}
void LocalDeviceManagement::on_mc_currentIndexChanged(int index)
{
if (ui->cemiFrame->text().size() >2)
m_fullCemiFrame = ui->cemiFrame->text();
int maxLength =10;
bool dataEnabled =true;
auto cemiFrame = m_fullCemiFrame;
quint8 data = ui->mc->itemData(index).toUInt();
switch (QKnxDeviceManagementFrame::MessageCode(data)) {
caseQKnxDeviceManagementFrame::MessageCode::PropertyReadRequest:
dataEnabled =false;
caseQKnxDeviceManagementFrame::MessageCode::PropertyWriteRequest:
maxLength =14;
caseQKnxDeviceManagementFrame::MessageCode::FunctionPropertyCommandRequest:
caseQKnxDeviceManagementFrame::MessageCode::FunctionPropertyStateReadRequest:
if (cemiFrame.size() < maxLength) {
cemiFrame.append(QStringLiteral("%1").arg(ui->noe->value(),1,16, QLatin1Char('0')));
cemiFrame.append(QStringLiteral("%1").arg(ui->startIndex->value(),3,16, QLatin1Char('0')));
}
cemiFrame =QStringLiteral("%1").arg(data,2,16, QLatin1Char('0')) + cemiFrame.mid(2);
break;
caseQKnxDeviceManagementFrame::MessageCode::ResetRequest:
maxLength =2;
dataEnabled =false;
cemiFrame =QStringLiteral("%1").arg(data,2,16, QLatin1Char('0'));
break;
default:
break;
}
ui->cemiFrame->setMaxLength(maxLength);
ui->cemiFrame->setText(cemiFrame);
ui->cemiData->setEnabled(dataEnabled);
}
void LocalDeviceManagement::on_objectType_currentTextChanged(constQString&type)
{
bool keyExists =false;
quint16 value = keyToValue(QKnxInterfaceObjectType::staticMetaObject, type,&keyExists);
if (keyExists) {
auto text = ui->cemiFrame->text();
ui->cemiFrame->setText(text.left(2) +QStringLiteral("%1").arg(value,4,16,
QLatin1Char('0')) + text.mid(6));
updatePropertyTypeCombobox(type);
}
}
void LocalDeviceManagement::on_property_currentTextChanged(constQString&property)
{
bool keyExists =false;
quint8 value = keyToValue(QKnxInterfaceObjectProperty::staticMetaObject, property,&keyExists);
if (keyExists) {
auto text = ui->cemiFrame->text();
ui->cemiFrame->setText(text.left(8) +QStringLiteral("%1").arg(value,2,16,
QLatin1Char('0')) + text.mid(10));
}
}
void LocalDeviceManagement::on_noe_valueChanged(int value)
{
auto text = ui->cemiFrame->text();
ui->cemiFrame->setText(text.left(10) +QStringLiteral("%1").arg(value,1,16, QLatin1Char('0')) + text.mid(11));
}
void LocalDeviceManagement::on_startIndex_valueChanged(int value)
{
auto text = ui->cemiFrame->text();
ui->cemiFrame->setText(text.left(11) +QStringLiteral("%1").arg(value,3,16,
QLatin1Char('0')));
}
void LocalDeviceManagement::on_objectInstance_valueChanged(int value)
{
auto text = ui->cemiFrame->text();
ui->cemiFrame->setText(text.left(6) +QStringLiteral("%1").arg(value,2,16,
QLatin1Char('0')) + text.mid(8));
}
void LocalDeviceManagement::on_manualInput_clicked(bool checked)
{
ui->cemiData->setEnabled(checked);
ui->cemiFrame->setReadOnly(!checked);
ui->cemiFrame->setMaxLength(SHRT_MAX);
ui->cemiFrame->setFocus();
if (checked) {
if (ui->mc->currentIndex() != ui->mc->count() -1)
m_fullCemiFrame = ui->cemiFrame->text();
} else {
ui->cemiFrame->setText(m_fullCemiFrame);
on_mc_currentIndexChanged(ui->mc->currentIndex());
}
}
void LocalDeviceManagement::onKeyringChanged(constQVector<QKnxNetIpSecureConfiguration>&configs)
{
m_configs = configs;
updateSecureConfigCombo();
}
void LocalDeviceManagement::setupMessageCodeComboBox()
{
ui->mc->addItem("M_PropRead.req",quint8(QKnxDeviceManagementFrame::MessageCode::PropertyReadRequest));
ui->mc->addItem("M_PropWrite.req",quint8(QKnxDeviceManagementFrame::MessageCode::PropertyWriteRequest));
ui->mc->addItem("M_FuncPropCommand.req",quint8(QKnxDeviceManagementFrame::MessageCode::FunctionPropertyCommandRequest));
ui->mc->addItem("M_FuncPropStateRead.req",quint8(QKnxDeviceManagementFrame::MessageCode::FunctionPropertyStateReadRequest));
ui->mc->addItem("M_Reset.req",quint8(QKnxDeviceManagementFrame::MessageCode::ResetRequest));
}
void LocalDeviceManagement::updatePropertyTypeCombobox(constQString&type)
{
int index =QKnxInterfaceObjectProperty::staticMetaObject.indexOfEnumerator("General");
if (index >=0) {
auto treeView = qobject_cast<QTreeWidget*>(ui->property->view());
if (!treeView)
return;
auto topLevelItem = treeView->takeTopLevelItem(0);
ui->property->clear();
treeView->addTopLevelItem(topLevelItem);
auto index =QKnxInterfaceObjectProperty::staticMetaObject.indexOfEnumerator(type.toLatin1());
if (index >=0) {
auto typeEnum =QKnxInterfaceObjectProperty::staticMetaObject.enumerator(index);
topLevelItem =newQTreeWidgetItem(treeView, { typeEnum.name() });
for (auto a =0; a < typeEnum.keyCount(); ++a)
topLevelItem->addChild(newQTreeWidgetItem({ typeEnum.key(a) }));
topLevelItem->setFlags(topLevelItem->flags() &~Qt::ItemIsSelectable);
}
treeView->expandItem(topLevelItem);
selectFirstSubitem(treeView, topLevelItem, ui->property);
}
}
void LocalDeviceManagement::handleIoListResponse(constQKnxDeviceManagementFrame&frame)
{
if (frame.objectType() !=QKnxInterfaceObjectType::System::Device
|| frame.property() !=QKnxInterfaceObjectProperty::Device::IoList) {
return;
}
if (frame.isNegativeConfirmation()) {
auto metaEnum =QMetaEnum::fromType<QKnxNetIp::CemiServer::Error>();
ui->textOuputDeviceManagement->append(tr("Received negative confirmation. Error code: %1")
.arg(QString::fromLatin1(metaEnum.valueToKey(int(frame.error())))));
m_awaitIoListResponse =false;
return;
}
auto dataTypes =QKnxInterfaceObjectPropertyDataType::fromProperty(QKnxInterfaceObjectProperty::Device::IoList);
if (!dataTypes.value(0).isValid())
return;
auto data = frame.data().toByteArray();
quint8 expectedDataSize = dataTypes[0].size();
if (frame.startIndex() ==0) {
if (data.size() == expectedDataSize) {
m_management.sendFrame(QKnxDeviceManagementFrame::propertyReadBuilder()
.setObjectType(QKnxInterfaceObjectType::System::Device)
.setObjectInstance(1)
.setProperty(QKnxInterfaceObjectProperty::Device::IoList)
.setNumberOfElements(data.toHex().toUShort(nullptr,16))
.setStartIndex(1)
.createRequest()
);
}
} else {
if ((data.size() % expectedDataSize) ==0) {
QSet<int> values;
for (int i =0; i < data.size(); i += expectedDataSize)
values.insert(data.mid(i, expectedDataSize).toHex().toUShort(nullptr,16));
m_awaitIoListResponse =false;
setupComboBox(ui->objectType,QKnxInterfaceObjectType::staticMetaObject, values);
}
}
}
int LocalDeviceManagement::keyToValue(constQMetaObject&object,constQString&key, bool *ok)
{
auto enumCount = object.enumeratorCount();
for (auto i =0; i < enumCount; ++i) {
int value = object.enumerator(i).keyToValue(key.toLatin1(), ok);
if (value !=-1&&*ok)
return value;
}
return-1;
}
void LocalDeviceManagement::setupComboBox(QComboBox*comboBox,constQMetaObject&object,constQSet<int>&values)
{
comboBox->clear();
auto treeWidget = qobject_cast<QTreeWidget*> (comboBox->view());
if (!treeWidget)
return;
QTreeWidgetItem*rootItem = nullptr;
auto enumCount = object.enumeratorCount();
for (auto i =0; i < enumCount; ++i) {
auto typeEnum = object.enumerator(i);
auto topLevelItem =newQTreeWidgetItem(treeWidget, { typeEnum.name() });
if (!rootItem)
rootItem = topLevelItem;
for (auto a =0; a < typeEnum.keyCount(); ++a) {
auto subItem =newQTreeWidgetItem(topLevelItem, { typeEnum.key(a) });
subItem->setDisabled(values.isEmpty() ?false : !values.contains(typeEnum.value(a)));
}
topLevelItem->setFlags(topLevelItem->flags() &~Qt::ItemIsSelectable);
}
treeWidget->expandItem(rootItem);
selectFirstSubitem(treeWidget, rootItem, comboBox);
}
void LocalDeviceManagement::selectFirstSubitem(QTreeWidget*treeWidget,QTreeWidgetItem*rootItem,QComboBox*comboBox)
{
treeWidget->setCurrentItem(rootItem,0);
comboBox->setRootModelIndex(treeWidget->currentIndex());
comboBox->setCurrentIndex(0);
treeWidget->setCurrentItem(treeWidget->invisibleRootItem(),0);
comboBox->setRootModelIndex(treeWidget->currentIndex());
}
void LocalDeviceManagement::updateSecureConfigCombo()
{
ui->secureSessionCb->clear();
ui->secureSessionCheckBox->setEnabled(!m_configs.isEmpty());
ui->secureSessionCheckBox->setChecked(m_proto ==QKnxNetIp::HostProtocol::TCP_IPv4);
for (int i =0; i < m_configs.size(); ++i) {
constauto&config = m_configs[i];
if (m_server.individualAddress() != config.host())
continue;
ui->secureSessionCb->addItem(tr("User ID: %1 (Individual Address: %2)").arg(config.userId())
.arg(config.individualAddress().toString()), i);
}
}