Discoverer Example

A CLI client for discovering KNX/netIP servers on the network.

Discoverer shows how to implement and start a discovery agent that discovers KNX/netIP servers on the network.

Usage

Below are the parameters that the client allows.

Usage: ./discoverer [options]

Options:
  -h, --help                         Displays this help.
  -t, --timeout <timeout>            Discovery response timeout in seconds.
  -n, --nat                          Use Network Address Translation to
                                     traverse across network routers.
  -u, --unicast                      Force unicast response. (defaults to
                                     multicast response)
  -a, --localAddress <localAddress>  The local IP address a response shall be
                                     sent to. Implies <unicast>
  -p, --localPort <localPort>        The local UDP port a response shall be
                                     sent to (defaults to system assigned).
                                     Implies <unicast>.
  -m, --searchMode <searchMode>      Specifies the mode used to send search
                                     request. Possible values: (default,
                                     extended, both).
  --filterProg                       Limit search responses to devices in
                                     programming mode. Implies search mode
                                     extended or both.
  --filterMAC <MAC>                  Limit search responses to the given MAC
                                     address. Implies search mode extended or
                                     both.
  --filterService <Service>          Limit search responses to devices
                                     supporting the given KNXnet/IP service
                                     family in at least the given version (e.g.
                                     0202). Implies search mode extended or
                                     both.
  --descriptionType <Type>           Force returning DIBs inside the search
                                     responses to to at least of the given set
                                     of IDs (e.g. 010208). Implies search mode
                                     extended or both.

By default the client uses the default network interface determined by the Operating System if no local IP address given. However, if an IP is given, the client will use the interface attached to that IP.

The following lines show a few examples of how to use the client:

./discoverer -m extended -a 127.0.0.1 -p 5543

Device used to send the search request:
  Network interface: lo, address: 127.0.0.1, port: 5543
No server(s) found on the network.

The command above uses the loopback interface. No KNX servers are available on this interface and therefore none are showed.

./discoverer -m extended
Device used to send the search request:
  Network interface: Unknown, address: 0.0.0.0, port: 0

1 server(s) found on the network.
  Server: IPR/S3.5.1 IP Router,MDRC
      Individual address: 1.1.0
      Server control endpoint: 10.9.78.33:3671
    Supported services:
      KNXnet/IP Core, Version: 2
      KNXnet/IP Device Management, Version: 2
      KNXnet/IP Tunnel, Version: 2
      KNXnet/IP Routing, Version: 2
      KNXnet/IP Remote Configuration, Version: 1
      KNXnet/IP Security, Version: 1
    Extended hardware information:
      Mask version: 091a
      Max. local APDU length: 254
      Medium status: Communication possible

Unlike the previous command example, this one uses the default network interface determined by the OS and shows that there is one KNX server on the network with 10.9.78.33 and listening on port 3671. The searchMode parameter refers to the KNXnet/IP Core version used. The extended mode indicates version 2 and can make use of extended search parameters.

    ./discoverer -m extended --filterService 0202

Device used to send the search request:
  Network interface: Unknown, address: 0.0.0.0, port: 0

1 server(s) found on the network.
  Server: IPR/S3.5.1 IP Router,MDRC
      Individual address: 1.1.0
      Server control endpoint: 10.9.78.33:3671
    Supported services:
      KNXnet/IP Core, Version: 2
      KNXnet/IP Device Management, Version: 2
      KNXnet/IP Tunnel, Version: 2
      KNXnet/IP Routing, Version: 2
      KNXnet/IP Remote Configuration, Version: 1
      KNXnet/IP Security, Version: 1
    Extended hardware information:
      Mask version: 091a
      Max. local APDU length: 254
      Medium status: Communication possible

The above command makes use of the extended search parameters available only in KNXnet/IP Core version 2. It limits the search responses to devices supporting the given KNXnet/IP service family 02 and in at least the given version 02.

In a network with two KNX routers, one of them only supporting KNXnet/IP Core Version 1, the output showed by the client with the default searchMode parameter set would be as follows:

./discoverer -m default

Device used to send the search request:
  Network interface: Unknown, address: 0.0.0.0, port: 0

2 server(s) found on the network.
  Server: IPR/S3.5.1 IP Router,MDRC
      Individual address: 1.1.0
      Server control endpoint: 10.9.78.33:3671
    Supported services:
      KNXnet/IP Core, Version: 2
      KNXnet/IP Device Management, Version: 2
      KNXnet/IP Tunnel, Version: 2
      KNXnet/IP Routing, Version: 2
      KNXnet/IP Remote Configuration, Version: 1

  Server: IPS/S3.1.1 IP-Schnittstelle,RE
      Individual address: 1.1.250
      Server control endpoint: 10.9.78.81:3671
    Supported services:
      KNXnet/IP Core, Version: 1
      KNXnet/IP Device Management, Version: 1
      KNXnet/IP Tunnel, Version: 1
      KNXnet/IP Remote Configuration, Version: 1

Implementation

The client delegates all the implementation of the KNX discovery to a QKnxNetIpServerDiscoveryAgent instance.

int main(int argc, char *argv[])
{
    ...
    QKnxNetIpServerDiscoveryAgent agent;

The discovery agent is started here:

int main(int argc, char *argv[])
{
    ...
    QKnxNetIpServerDiscoveryAgent agent;
    ...
    QObject::connect(&agent, &QKnxNetIpServerDiscoveryAgent::finished, &discoverer,
        &QCoreApplication::quit);

    if (!parser.isSet("localAddress")) {
        agent.start(QKnxNetIpServerDiscoveryAgent::InterfaceType::Ethernet
            | QKnxNetIpServerDiscoveryAgent::InterfaceType::Wifi);
    } else {
        agent.start(QVector<QHostAddress> { agent.localAddress() });
    }

    if (agent.error() == QKnxNetIpServerDiscoveryAgent::Error::None
        && agent.state() == QKnxNetIpServerDiscoveryAgent::State::Running) {
            discoverer.exec();
    ...
}

When the agent finishes discovering the servers, the main function of the client is resumed. The above code snippet shows how the QKnxNetIpServerDiscoveryAgent::finished() signal triggers a call to QCoreApplication::quit(). The client execution then keeps going after the QCoreApplication::exec().

Finally, we recover a list of servers and output the information found:

int main(int argc, char *argv[])
{
    ...
    if (agent.error() == QKnxNetIpServerDiscoveryAgent::Error::None
        && agent.state() == QKnxNetIpServerDiscoveryAgent::State::Running) {
            discoverer.exec();
    }

    const auto servers = agent.discoveredServers();
    if (servers.size() > 0) {
        qInfo().noquote() << Qt::endl << QString::fromLatin1("%1 server(s) found on the network.")
            .arg(servers.size());
        for (auto server : servers) {
            qInfo().noquote() << QString::fromLatin1("  Network interface: %1, address: %2")
                .arg(server.networkInterface().humanReadableName(), server.hostAddress().toString());
            qInfo().noquote() << QString::fromLatin1("  Server: %1").arg(server.deviceName());
            qInfo().noquote() << QString::fromLatin1("      Individual address: %1").arg(server
                .individualAddress().toString());
            qInfo().noquote() << QString::fromLatin1("      Server control endpoint: %1:%2")
                .arg(server.controlEndpointAddress().toString()).arg(server.controlEndpointPort());

            const auto services = server.supportedServices();
            qInfo().noquote() << QString::fromLatin1("    Supported services:");
            for (const auto service : services) {
                qInfo().noquote() << QString::fromLatin1("      KNXnet/IP %1, Version: %2")
                    .arg(familyToString(service.ServiceFamily)).arg(service.ServiceFamilyVersion);
            }

            const auto dib = server.extendedHardware();
            QKnxNetIpExtendedDeviceDibProxy hardware(dib);
            if (hardware.isValid()) {
                qInfo() << "    Extended hardware information:";
                qInfo().noquote() << QString::fromLatin1("      Mask version: %1").arg(hardware
                    .deviceDescriptorType0(), 4, 16, QLatin1Char('0'));
                qInfo().noquote() << QString::fromLatin1("      Max. local APDU length: %1")
                    .arg(hardware.maximumLocalApduLength());

                auto status = server.mediumStatus();
                if (status == QKnx::MediumStatus::CommunicationPossible)
                    qInfo() << "      Medium status: Communication possible";
                else if (status == QKnx::MediumStatus::CommunicationImpossible)
                    qInfo() << "      Medium status: Communication impossible";
                else
                    qInfo() << "      Medium status: Unknown";
            }
            qInfo() << "";
        }
    } else {
        qInfo().noquote() << "No server(s) found on the network.";
    }

    return 0;
}

Files:

© 2021 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.