A KNX client for handling KNXnet/IP local device management.
The KNX local device management example user interface contains various Qt Widgets, most prominently a 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 接口 field. Once that is done, the application automatically performs a continuous search for available KNXnet/IP devices and displays the results in the 设备 字段。
To connect to a KNXnet/IP device, either the one preselected in the 设备 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 File 菜单。
Once a connection is successfully established, the user has the possibility to issue a property read or write command to an existing
Interface Object
in the management server. The example currently limits the possible read or write operations that can be used to
M_PropRead.req
and
m_PropWrite.req
messages.
The application consists of two classes:
MainWindow
是
QMainWindow
that renders the general layout of the application.
DeviceItem
是
QStandardItem
that is used to display and store information about discovered KNXnet/IP devices.
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; };
The
MainWindow
class uses a
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
QKnxNetIpDeviceManagement
used to establish the connection to the KNXnet/Ip device and a list of imported KNX
QKnxNetIpSecureConfiguration
secure configurations.
There are signal handlers installed for every signal emitted by the 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:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ... 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); ... } void MainWindow::on_connection_clicked() { if (ui->devices->count() <= 0) return; if (m_management.state() == QKnxNetIpTunnel::State::Connected) return m_management.disconnectFromHost(); const auto list = ui->interfaces->currentData().toStringList(); m_management.setLocalAddress(QHostAddress(list.first())); m_management.setSerialNumber(QKnxByteArray::fromHex(list.last().toLatin1())); 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->currentData().toInt()); 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); } }
The
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:
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ... 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(); }
There is a signal handler installed for the device discovered signal emitted by the discovery agent. When the signal
QKnxNetIpServerDiscoveryAgent::deviceDiscovered
is triggered, the function
MainWindow::onDeviceDiscovered()
is called. It adds a new device item to the
设备
if it is not already there.
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())); }
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
MainWindow::on_devices_currentIndexChanged
method saves the selected KNXnet/IP device in the the
MainWindow
实例。
To populate the
Interface object type
drop-down box with available KNX interface object types, the function
MainWindow::populateInterfaceObjectTypesComboBox
is invoked. When the application user switches the currently selected object type to a different one, the
Property ID
gets updated with matching property IDs for the active interface object:
void MainWindow::on_interfaceObjectTypes_currentTextChanged(const QString &type) { ... 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); } ... }
In this last example, when the user triggers the
读取
button, the function
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:
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); }
The KNX local device management example
main()
function does not have any special handling. It looks like the main function for any Qt app:
#include "mainwindow.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec(); }
文件: