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: