This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 45a2aa6331ffe9ccc9dcd5357ed0b22f313471ac
Author: Christofer Dutz <[email protected]>
AuthorDate: Sat Jul 11 13:40:47 2020 +0200

    - Made the KNXnet/IP driver use a LINK-LAYER connection instead of a 
Busmonitor connection (per default)
    - Made it configurable which type of connection should be used
    - Updated the mspec to support link-layer data
    - Added documentation on the KNXnet/IP driver usage
---
 RELEASE_NOTES                                      |   8 +-
 .../configuration/KnxNetIpConfiguration.java       |  22 +++
 .../knxnetip/protocol/KnxNetIpProtocolLogic.java   | 149 ++++++++++++---------
 .../test/resources/testsuite/KNXNetIPTestsuite.xml | 104 +++++++++++++-
 plc4j/examples/pom.xml                             |   3 +-
 .../resources/protocols/knxnetip/knxnetip.mspec    |  65 +++++++--
 src/site/asciidoc/users/protocols/knxnetip.adoc    |  79 +++++++++++
 7 files changed, 352 insertions(+), 78 deletions(-)

diff --git a/RELEASE_NOTES b/RELEASE_NOTES
index a0551b1..fe460e5 100644
--- a/RELEASE_NOTES
+++ b/RELEASE_NOTES
@@ -17,9 +17,11 @@ Bug Fixes
 
 PLC4X-206   When writing short values exceptions are thrown
             while preparing the write request.
-PLC4X-208   [S7] When trying to write to a S7 device and writing
-            is not explicitly enabled, the PLC will respond with
-            an error code
+PLC4X-209   [S7] When writing INT and DINT values the Write
+            operation fails with an internal error
+PLC4X-210   [KNX] When running a KNX Tunneling Subscription
+            for a longer time there are packets that kill
+            the connection
 
 ==============================================================
 Apache PLC4X 0.7.0
diff --git 
a/plc4j/drivers/knxnetip/src/main/java/org/apache/plc4x/java/knxnetip/configuration/KnxNetIpConfiguration.java
 
b/plc4j/drivers/knxnetip/src/main/java/org/apache/plc4x/java/knxnetip/configuration/KnxNetIpConfiguration.java
index 7ce150d..6a0b8e5 100644
--- 
a/plc4j/drivers/knxnetip/src/main/java/org/apache/plc4x/java/knxnetip/configuration/KnxNetIpConfiguration.java
+++ 
b/plc4j/drivers/knxnetip/src/main/java/org/apache/plc4x/java/knxnetip/configuration/KnxNetIpConfiguration.java
@@ -19,11 +19,14 @@ under the License.
 package org.apache.plc4x.java.knxnetip.configuration;
 
 import org.apache.plc4x.java.knxnetip.KnxNetIpDriver;
+import org.apache.plc4x.java.knxnetip.readwrite.types.KnxLayer;
 import org.apache.plc4x.java.spi.configuration.Configuration;
 import 
org.apache.plc4x.java.spi.configuration.annotations.ConfigurationParameter;
 import 
org.apache.plc4x.java.spi.configuration.annotations.defaults.BooleanDefaultValue;
 import 
org.apache.plc4x.java.spi.configuration.annotations.defaults.FloatDefaultValue;
 import 
org.apache.plc4x.java.spi.configuration.annotations.defaults.IntDefaultValue;
+import 
org.apache.plc4x.java.spi.configuration.annotations.defaults.StringDefaultValue;
+import 
org.apache.plc4x.java.spi.configuration.exceptions.ConfigurationException;
 import 
org.apache.plc4x.java.transport.pcapreplay.PcapReplayTransportConfiguration;
 import 
org.apache.plc4x.java.transport.rawsocket.RawSocketTransportConfiguration;
 import org.apache.plc4x.java.transport.udp.UdpTransportConfiguration;
@@ -39,6 +42,10 @@ public class KnxNetIpConfiguration implements Configuration, 
UdpTransportConfigu
     @IntDefaultValue(3)
     public int groupAddressType = 3;
 
+    @ConfigurationParameter("connection-type")
+    @StringDefaultValue("LINK_LAYER")
+    public String connectionType = "LINK_LAYER";
+
     @ConfigurationParameter("replay-speed-factor")
     @FloatDefaultValue(1.0f)
     public float replaySpeedFactor = 1.0f;
@@ -63,6 +70,21 @@ public class KnxNetIpConfiguration implements Configuration, 
UdpTransportConfigu
         this.groupAddressType = groupAddressType;
     }
 
+    public String getConnectionType() {
+        return connectionType;
+    }
+
+    public void setConnectionType(String connectionType) {
+        // Try to parse the provided value, if it doesn't match any of the 
constants,
+        // throw an error.
+        try {
+            KnxLayer.valueOf("TUNNEL_" + connectionType.toUpperCase());
+        } catch (IllegalArgumentException e) {
+            throw new ConfigurationException("Value provided for 
connection-type invalid.");
+        }
+        this.connectionType = connectionType.toUpperCase();
+    }
+
     @Override
     public float getReplaySpeedFactor() {
         return replaySpeedFactor;
diff --git 
a/plc4j/drivers/knxnetip/src/main/java/org/apache/plc4x/java/knxnetip/protocol/KnxNetIpProtocolLogic.java
 
b/plc4j/drivers/knxnetip/src/main/java/org/apache/plc4x/java/knxnetip/protocol/KnxNetIpProtocolLogic.java
index cf6d34e..4cd636b 100644
--- 
a/plc4j/drivers/knxnetip/src/main/java/org/apache/plc4x/java/knxnetip/protocol/KnxNetIpProtocolLogic.java
+++ 
b/plc4j/drivers/knxnetip/src/main/java/org/apache/plc4x/java/knxnetip/protocol/KnxNetIpProtocolLogic.java
@@ -50,6 +50,7 @@ import 
org.apache.plc4x.java.knxnetip.readwrite.types.HostProtocolCode;
 import org.apache.plc4x.java.knxnetip.readwrite.types.KnxLayer;
 import org.apache.plc4x.java.knxnetip.readwrite.types.Status;
 import org.apache.plc4x.java.spi.configuration.HasConfiguration;
+import org.apache.plc4x.java.spi.generation.ParseException;
 import org.apache.plc4x.java.spi.generation.ReadBuffer;
 import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionEvent;
 import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionResponse;
@@ -80,10 +81,12 @@ public class KnxNetIpProtocolLogic extends 
Plc4xProtocolBase<KNXNetIPMessage> im
     private IPAddress localIPAddress;
     private int localPort;
     private short communicationChannelId;
+    private KNXAddress clientKnxAddress;
 
     private Timer connectionStateTimer;
 
     private byte groupAddressType;
+    private KnxLayer tunnelConnectionType;
     private Ets5Model ets5Model;
 
     private Map<DefaultPlcConsumerRegistration, 
Consumer<PlcSubscriptionEvent>> consumers = new ConcurrentHashMap<>();
@@ -103,6 +106,7 @@ public class KnxNetIpProtocolLogic extends 
Plc4xProtocolBase<KNXNetIPMessage> im
         } else {
             groupAddressType = (byte) configuration.groupAddressType;
         }
+        tunnelConnectionType = KnxLayer.valueOf("TUNNEL_" + 
configuration.getConnectionType());
     }
 
     @Override
@@ -146,7 +150,7 @@ public class KnxNetIpProtocolLogic extends 
Plc4xProtocolBase<KNXNetIPMessage> im
                         ConnectionRequest connectionRequest = new 
ConnectionRequest(
                             new 
HPAIDiscoveryEndpoint(HostProtocolCode.IPV4_UDP, localIPAddress, localPort),
                             new HPAIDataEndpoint(HostProtocolCode.IPV4_UDP, 
localIPAddress, localPort),
-                            new 
ConnectionRequestInformationTunnelConnection(KnxLayer.TUNNEL_BUSMONITOR));
+                            new 
ConnectionRequestInformationTunnelConnection(tunnelConnectionType));
                         LOGGER.info("Sending KNXnet/IP Connection Request.");
                         context.sendRequest(connectionRequest)
                             .expectResponse(KNXNetIPMessage.class, 
Duration.ofMillis(1000))
@@ -162,8 +166,15 @@ public class KnxNetIpProtocolLogic extends 
Plc4xProtocolBase<KNXNetIPMessage> im
                                 // Check if everything went well.
                                 Status status = connectionResponse.getStatus();
                                 if (status == Status.NO_ERROR) {
-                                    LOGGER.info(String.format("Successfully 
connected to KNXnet/IP Gateway '%s' with KNX address '%d.%d.%d'", gatewayName,
-                                        gatewayAddress.getMainGroup(), 
gatewayAddress.getMiddleGroup(), gatewayAddress.getSubGroup()));
+                                    final 
ConnectionResponseDataBlockTunnelConnection tunnelConnectionDataBlock =
+                                        
(ConnectionResponseDataBlockTunnelConnection) 
connectionResponse.getConnectionResponseDataBlock();
+                                    // Save the KNX Address the Gateway 
assigned to this connection.
+                                    clientKnxAddress = 
tunnelConnectionDataBlock.getKnxAddress();
+
+                                    LOGGER.info(String.format("Successfully 
connected to KNXnet/IP Gateway '%s' with KNX address '%d.%d.%d' got assigned 
client KNX address '%d.%d.%d'", gatewayName,
+                                        gatewayAddress.getMainGroup(), 
gatewayAddress.getMiddleGroup(),
+                                        gatewayAddress.getSubGroup(), 
clientKnxAddress.getMainGroup(),
+                                        clientKnxAddress.getMiddleGroup(), 
clientKnxAddress.getSubGroup()));
 
                                     // Send an event that connection setup is 
complete.
                                     context.fireConnected();
@@ -253,70 +264,20 @@ public class KnxNetIpProtocolLogic extends 
Plc4xProtocolBase<KNXNetIPMessage> im
             // Only if the communication channel id match, do anything with 
the request.
             // In case of a passive-mode driver we'll simply accept all 
communication ids.
             if(passiveMode || (curCommunicationChannelId == 
communicationChannelId)) {
-                if(tunnelingRequest.getCemi() instanceof CEMIBusmonInd) {
+                // Data packets received from a link layer tunneling 
connection.
+                if(tunnelingRequest.getCemi() instanceof CEMIDataInd) {
+                    CEMIDataInd dataInd = (CEMIDataInd) 
tunnelingRequest.getCemi();
+                    final CEMIDataIndFrame cemiDataFrame = 
dataInd.getCemiFrame();
+                    processCemiData(cemiDataFrame.getSourceAddress(), 
cemiDataFrame.getDestinationAddress(),
+                        cemiDataFrame.getDataFirstByte(), 
cemiDataFrame.getData());
+                }
+                // Data packets received from a busmonitor tunneling 
connection.
+                else if(tunnelingRequest.getCemi() instanceof CEMIBusmonInd) {
                     CEMIBusmonInd busmonInd = (CEMIBusmonInd) 
tunnelingRequest.getCemi();
                     if (busmonInd.getCemiFrame() instanceof CEMIFrameData) {
                         CEMIFrameData cemiDataFrame = (CEMIFrameData) 
busmonInd.getCemiFrame();
-
-                        // The first byte is actually just 6 bit long, but 
we'll treat it as a full one.
-                        // So here we create a byte array containing the first 
and all the following bytes.
-                        byte[] payload = new byte[1 + 
cemiDataFrame.getData().length];
-                        payload[0] = cemiDataFrame.getDataFirstByte();
-                        System.arraycopy(cemiDataFrame.getData(), 0, payload, 
1, cemiDataFrame.getData().length);
-
-                        final KNXAddress sourceAddress = 
cemiDataFrame.getSourceAddress();
-                        final byte[] destinationGroupAddress = 
cemiDataFrame.getDestinationAddress();
-
-                        // Decode the group address depending on the project 
settings.
-                        ReadBuffer addressBuffer = new 
ReadBuffer(destinationGroupAddress);
-                        final KNXGroupAddress knxGroupAddress =
-                            KNXGroupAddressIO.staticParse(addressBuffer, 
groupAddressType);
-                        final String destinationAddress = 
toString(knxGroupAddress);
-
-                        // If there is an ETS5 model provided, continue 
decoding the payload.
-                        if (ets5Model != null) {
-                            final GroupAddress groupAddress = 
ets5Model.getGroupAddresses().get(destinationAddress);
-
-                            if ((groupAddress != null) && 
(groupAddress.getType() != null)) {
-                                LOGGER.info(String.format("Message from: '%s' 
to: '%s'",
-                                    toString(sourceAddress), 
destinationAddress));
-
-                                // Parse the payload depending on the type of 
the group-address.
-                                ReadBuffer rawDataReader = new 
ReadBuffer(payload);
-                                final PlcValue value = 
KnxDatapointIO.staticParse(rawDataReader,
-                                    groupAddress.getType().getMainType(), 
groupAddress.getType().getSubType());
-
-                                // Assemble the plc4x return data-structure.
-                                Map<String, PlcValue> dataPointMap = new 
HashMap<>();
-                                dataPointMap.put("sourceAddress", new 
PlcString(toString(sourceAddress)));
-                                dataPointMap.put("targetAddress", new 
PlcString(groupAddress.getGroupAddress()));
-                                if (groupAddress.getFunction() != null) {
-                                    dataPointMap.put("location", new 
PlcString(groupAddress.getFunction().getSpaceName()));
-                                    dataPointMap.put("function", new 
PlcString(groupAddress.getFunction().getName()));
-                                } else {
-                                    dataPointMap.put("location", null);
-                                    dataPointMap.put("function", null);
-                                }
-                                dataPointMap.put("description", new 
PlcString(groupAddress.getName()));
-                                dataPointMap.put("unitOfMeasurement", new 
PlcString(groupAddress.getType().getName()));
-                                dataPointMap.put("value", value);
-                                final PlcStruct dataPoint = new 
PlcStruct(dataPointMap);
-
-                                // Send the data-structure.
-                                publishEvent(groupAddress, dataPoint);
-                            } else {
-                                LOGGER.warn(
-                                    String.format("Message from: '%s' to 
unknown group address: '%s'%n payload: '%s'",
-                                        toString(sourceAddress), 
destinationAddress, Hex.encodeHexString(payload)));
-                            }
-                        }
-                        // Else just output the raw payload.
-                        else {
-                            LOGGER.info(String.format("Raw Message: '%s' to: 
'%s'%n payload: '%s'",
-                                KnxNetIpProtocolLogic.toString(sourceAddress), 
destinationAddress,
-                                Hex.encodeHexString(payload))
-                            );
-                        }
+                        processCemiData(cemiDataFrame.getSourceAddress(), 
cemiDataFrame.getDestinationAddress(),
+                            cemiDataFrame.getDataFirstByte(), 
cemiDataFrame.getData());
                     }
                 }
 
@@ -329,6 +290,66 @@ public class KnxNetIpProtocolLogic extends 
Plc4xProtocolBase<KNXNetIPMessage> im
         }
     }
 
+    protected void processCemiData(KNXAddress sourceAddress, byte[] 
destinationGroupAddress,
+                                   byte firstByte, byte[] restBytes) throws 
ParseException {
+        // The first byte is actually just 6 bit long, but we'll treat it as a 
full one.
+        // So here we create a byte array containing the first and all the 
following bytes.
+        byte[] payload = new byte[1 + restBytes.length];
+        payload[0] = firstByte;
+        System.arraycopy(restBytes, 0, payload, 1, restBytes.length);
+
+        // Decode the group address depending on the project settings.
+        ReadBuffer addressBuffer = new ReadBuffer(destinationGroupAddress);
+        final KNXGroupAddress knxGroupAddress =
+            KNXGroupAddressIO.staticParse(addressBuffer, groupAddressType);
+        final String destinationAddress = toString(knxGroupAddress);
+
+        // If there is an ETS5 model provided, continue decoding the payload.
+        if (ets5Model != null) {
+            final GroupAddress groupAddress = 
ets5Model.getGroupAddresses().get(destinationAddress);
+
+            if ((groupAddress != null) && (groupAddress.getType() != null)) {
+                LOGGER.info(String.format("Message from: '%s' to: '%s'",
+                    toString(sourceAddress), destinationAddress));
+
+                // Parse the payload depending on the type of the 
group-address.
+                ReadBuffer rawDataReader = new ReadBuffer(payload);
+                final PlcValue value = 
KnxDatapointIO.staticParse(rawDataReader,
+                    groupAddress.getType().getMainType(), 
groupAddress.getType().getSubType());
+
+                // Assemble the plc4x return data-structure.
+                Map<String, PlcValue> dataPointMap = new HashMap<>();
+                dataPointMap.put("sourceAddress", new 
PlcString(toString(sourceAddress)));
+                dataPointMap.put("targetAddress", new 
PlcString(groupAddress.getGroupAddress()));
+                if (groupAddress.getFunction() != null) {
+                    dataPointMap.put("location", new 
PlcString(groupAddress.getFunction().getSpaceName()));
+                    dataPointMap.put("function", new 
PlcString(groupAddress.getFunction().getName()));
+                } else {
+                    dataPointMap.put("location", null);
+                    dataPointMap.put("function", null);
+                }
+                dataPointMap.put("description", new 
PlcString(groupAddress.getName()));
+                dataPointMap.put("unitOfMeasurement", new 
PlcString(groupAddress.getType().getName()));
+                dataPointMap.put("value", value);
+                final PlcStruct dataPoint = new PlcStruct(dataPointMap);
+
+                // Send the data-structure.
+                publishEvent(groupAddress, dataPoint);
+            } else {
+                LOGGER.warn(
+                    String.format("Message from: '%s' to unknown group 
address: '%s'%n payload: '%s'",
+                        toString(sourceAddress), destinationAddress, 
Hex.encodeHexString(payload)));
+            }
+        }
+        // Else just output the raw payload.
+        else {
+            LOGGER.info(String.format("Raw Message: '%s' to: '%s'%n payload: 
'%s'",
+                KnxNetIpProtocolLogic.toString(sourceAddress), 
destinationAddress,
+                Hex.encodeHexString(payload))
+            );
+        }
+    }
+
     @Override
     public void close(ConversationContext<KNXNetIPMessage> context) {
         // TODO Implement Closing on Protocol Level
diff --git 
a/plc4j/drivers/knxnetip/src/test/resources/testsuite/KNXNetIPTestsuite.xml 
b/plc4j/drivers/knxnetip/src/test/resources/testsuite/KNXNetIPTestsuite.xml
index 5f52c17..13ed46f 100644
--- a/plc4j/drivers/knxnetip/src/test/resources/testsuite/KNXNetIPTestsuite.xml
+++ b/plc4j/drivers/knxnetip/src/test/resources/testsuite/KNXNetIPTestsuite.xml
@@ -21,6 +21,99 @@
 
   <name>KNXNet/IP</name>
 
+  <!--testcase>
+    <name>Causes Failure 1</name>
+    <raw>0610042000180404ce002b0703010404025002bab8b838bb</raw>
+    Raw CEMI Frame: bab8b838bb
+    Raw CEMI Frame: ba
+
+    Decoded as Extended Frame Format:
+    group address: true
+    hop count: 3
+    extended frame format: 8 (1 0 0 0)
+    source address: 11/8/56
+
+    <raw>061004200018047ddf002b07030104040207029f9c9c9cdc</raw>
+    Raw CEMI Frame: 9f9c9c9cdc
+    Control Field: 9f
+
+    Differences from normal:
+    Repeat: True
+    Last two reserved bits are true
+    <root-type>KNXNetIPMessage</root-type>
+    <xml>
+      <TunnelingRequest 
className="org.apache.plc4x.java.knxnetip.readwrite.TunnelingRequest">
+        <tunnelingRequestDataBlock 
className="org.apache.plc4x.java.knxnetip.readwrite.TunnelingRequestDataBlock">
+          <communicationChannelId>125</communicationChannelId>
+          <sequenceCounter>223</sequenceCounter>
+        </tunnelingRequestDataBlock>
+        <cemi 
className="org.apache.plc4x.java.knxnetip.readwrite.CEMIBusmonInd">
+          <additionalInformationLength>7</additionalInformationLength>
+          <additionalInformation>
+            <additionalInformation 
className="org.apache.plc4x.java.knxnetip.readwrite.CEMIAdditionalInformationBusmonitorInfo">
+              <frameErrorFlag>false</frameErrorFlag>
+              <bitErrorFlag>false</bitErrorFlag>
+              <parityErrorFlag>false</parityErrorFlag>
+              <unknownFlag>false</unknownFlag>
+              <lostFlag>false</lostFlag>
+              <sequenceNumber>4</sequenceNumber>
+            </additionalInformation>
+            <additionalInformation 
className="org.apache.plc4x.java.knxnetip.readwrite.CEMIAdditionalInformationRelativeTimestamp">
+              <relativeTimestamp 
className="org.apache.plc4x.java.knxnetip.readwrite.RelativeTimestamp">
+                <timestamp>1794</timestamp>
+              </relativeTimestamp>
+            </additionalInformation>
+          </additionalInformation>
+          <cemiFrame 
className="org.apache.plc4x.java.knxnetip.readwrite.CEMIFramePollingData">
+            <doNotRepeat>false</doNotRepeat>
+            <priority>LOW</priority>
+            <error>true</error>
+          </cemiFrame>
+        </cemi>
+      </TunnelingRequest>
+    </xml>
+  </testcase>
+
+  <testcase>
+    <name>Causes Failure 2</name>
+    <raw>0610042000180401c2002b0703010304025601bab8b838bb</raw>
+    Raw CEMI Frame: bab8b838bb
+    Control Field: ba
+    First of the last two reserved bits is true
+    <root-type>KNXNetIPMessage</root-type>
+    <xml>
+      <TunnelingRequest 
className="org.apache.plc4x.java.knxnetip.readwrite.TunnelingRequest">
+        <tunnelingRequestDataBlock 
className="org.apache.plc4x.java.knxnetip.readwrite.TunnelingRequestDataBlock">
+          <communicationChannelId>1</communicationChannelId>
+          <sequenceCounter>194</sequenceCounter>
+        </tunnelingRequestDataBlock>
+        <cemi 
className="org.apache.plc4x.java.knxnetip.readwrite.CEMIBusmonInd">
+          <additionalInformationLength>7</additionalInformationLength>
+          <additionalInformation>
+            <additionalInformation 
className="org.apache.plc4x.java.knxnetip.readwrite.CEMIAdditionalInformationBusmonitorInfo">
+              <frameErrorFlag>false</frameErrorFlag>
+              <bitErrorFlag>false</bitErrorFlag>
+              <parityErrorFlag>false</parityErrorFlag>
+              <unknownFlag>false</unknownFlag>
+              <lostFlag>false</lostFlag>
+              <sequenceNumber>3</sequenceNumber>
+            </additionalInformation>
+            <additionalInformation 
className="org.apache.plc4x.java.knxnetip.readwrite.CEMIAdditionalInformationRelativeTimestamp">
+              <relativeTimestamp 
className="org.apache.plc4x.java.knxnetip.readwrite.RelativeTimestamp">
+                <timestamp>22017</timestamp>
+              </relativeTimestamp>
+            </additionalInformation>
+          </additionalInformation>
+          <cemiFrame 
className="org.apache.plc4x.java.knxnetip.readwrite.CEMIFramePollingData">
+            <doNotRepeat>true</doNotRepeat>
+            <priority>URGENT</priority>
+            <error>false</error>
+          </cemiFrame>
+        </cemi>
+      </TunnelingRequest>
+    </xml>
+  </testcase-->
+
   <testcase>
     <name>Search Request</name>
     <raw>06100201000e0801c0a82a46ef8e</raw>
@@ -310,6 +403,10 @@
   <testcase>
     <name>Tunneling Request</name>
     <raw>06100420001c046b00002b0703010504024502bc360a1e0ce100810d</raw>
+    <!--
+    Raw CEMI Frame: bc360a1e0ce100810d
+    Control Field: bc
+    -->
     <root-type>KNXNetIPMessage</root-type>
     <xml>
       <TunnelingRequest 
className="org.apache.plc4x.java.knxnetip.readwrite.TunnelingRequest">
@@ -335,8 +432,10 @@
             </additionalInformation>
           </additionalInformation>
           <cemiFrame 
className="org.apache.plc4x.java.knxnetip.readwrite.CEMIFrameData">
-            <doNotRepeat>true</doNotRepeat>
+            <repeated>true</repeated>
             <priority>LOW</priority>
+            <acknowledgeRequested>false</acknowledgeRequested>
+            <error>false</error>
             <sourceAddress 
className="org.apache.plc4x.java.knxnetip.readwrite.KNXAddress">
               <mainGroup>3</mainGroup>
               <middleGroup>6</middleGroup>
@@ -346,7 +445,8 @@
             <groupAddress>true</groupAddress>
             <hopCount>6</hopCount>
             <dataLength>1</dataLength>
-            <tpci>0</tpci>
+            <tcpi>UNNUMBERED_DATA_PACKET</tcpi>
+            <counter>0</counter>
             <apci>GROUP_VALUE_WRITE_PDU</apci>
             <dataFirstByte>1</dataFirstByte>
             <data></data>
diff --git a/plc4j/examples/pom.xml b/plc4j/examples/pom.xml
index 7b248ff..bc34448 100644
--- a/plc4j/examples/pom.xml
+++ b/plc4j/examples/pom.xml
@@ -45,11 +45,12 @@
     <module>hello-connectivity-kafka</module>
     <module>hello-connectivity-mqtt</module>
     <module>hello-integration-edgent</module>
+    <module>hello-integration-iotdb</module>
     <module>hello-opm</module>
     <module>hello-storage-elasticsearch</module>
     <module>hello-webapp</module>
     <module>hello-world-plc4x</module>
-    <module>hello-integration-iotdb</module>
+    <module>hello-world-plc4x-subscription</module>
   </modules>
 
   <build>
diff --git 
a/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec 
b/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec
index 7fa200b..336ce3e 100644
--- a/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec
+++ b/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec
@@ -230,6 +230,7 @@
     ]
 ]
 
+/* The CEMI part is described in the document "03_06_03 EMI_IMI v01.03.03 AS" 
*/
 [discriminatedType 'CEMI' [uint 8 'size']
     [discriminator uint 8 'messageCode']
     [typeSwitch 'messageCode'
@@ -243,6 +244,9 @@
         ['0x25' CEMIPollDataCon
         ]
         ['0x29' CEMIDataInd
+            [simple uint 8                    'additionalInformationLength']
+            [array  CEMIAdditionalInformation 'additionalInformation' length 
'additionalInformationLength']
+            [simple CEMIDataIndFrame          'cemiFrame']
         ]
         ['0x2B' CEMIBusmonInd
             [simple uint 8                    'additionalInformationLength']
@@ -277,7 +281,7 @@
     [discriminator uint 8 'additionalInformationType']
     [typeSwitch 'additionalInformationType'
         ['0x03' CEMIAdditionalInformationBusmonitorInfo
-            [implicit  uint 8 'len' '1']
+            [const     uint 8 'len' '1']
             [simple    bit    'frameErrorFlag']
             [simple    bit    'bitErrorFlag']
             [simple    bit    'parityErrorFlag']
@@ -286,19 +290,43 @@
             [simple    uint 3 'sequenceNumber']
         ]
         ['0x04' CEMIAdditionalInformationRelativeTimestamp
-            [implicit uint 8            'len' '2']
+            [const    uint 8            'len' '2']
             [simple   RelativeTimestamp 'relativeTimestamp']
         ]
     ]
 ]
 
+[type 'CEMIDataIndFrame'
+    [simple        bit          'standardFrame']
+    [simple        bit          'polling']
+    [simple        bit          'notRepeated']
+    [simple        bit          'notAckFrame']
+    [enum          CEMIPriority 'priority']
+    [simple        bit          'acknowledgeRequested']
+    [simple        bit          'error']
+    [simple        bit          'groupDestinationAddress']
+    [simple        uint 3       'hopCount']
+    [simple        uint 4       'extendedFrameFormat']
+    [simple        KNXAddress   'sourceAddress']
+    [array         int 8        'destinationAddress' count '2']
+    [simple        uint 8       'dataLength']
+    [enum          TPCI         'tcpi']
+    [simple        uint 4       'counter']
+    [enum          APCI         'apci']
+    [simple        int 6        'dataFirstByte']
+    [array         int 8        'data' count 'dataLength - 1']
+]
+
+/* The CEMI part is described in the document "03_06_03 EMI_IMI v01.03.03 AS" 
Page 73
+"03_02_02 Communication Medium TP1 v01.02.02 AS" Page 27 */
 [discriminatedType 'CEMIFrame'
     [discriminator bit          'standardFrame']
     [discriminator bit          'polling']
-    [simple        bit          'doNotRepeat']
+    [simple        bit          'repeated']
     [discriminator bit          'notAckFrame']
     [enum          CEMIPriority 'priority']
-    [reserved      uint 2       '0x0']
+    [simple        bit          'acknowledgeRequested']
+    [simple        bit          'error']
     [typeSwitch 'notAckFrame','standardFrame','polling'
         ['false' CEMIFrameAck
         ]
@@ -308,12 +336,15 @@
             [simple   bit             'groupAddress']
             [simple   uint 3          'hopCount']
             [simple   uint 4          'dataLength']
-            [simple   uint 6          'tpci']
+            [enum     TPCI            'tcpi']
+            [simple   uint 4          'counter']
             [enum     APCI            'apci']
             [simple   int 6           'dataFirstByte']
             [array    int 8           'data' count 'dataLength - 1']
             [simple   uint 8          'crc']
         ]
+        ['true','true','true' CEMIFramePollingData
+        ]
         ['true','false','false' CEMIFrameDataExt
             [simple   bit             'groupAddress']
             [simple   uint 3          'hopCount']
@@ -321,14 +352,13 @@
             [simple   KNXAddress      'sourceAddress']
             [array    int 8           'destinationAddress' count '2']
             [simple   uint 8          'dataLength']
-            [simple   uint 6          'tpci']
+            [enum     TPCI            'tcpi']
+            [simple   uint 4          'counter']
             [enum     APCI            'apci']
             [simple   int 6           'dataFirstByte']
             [array    int 8           'data' count 'dataLength - 1']
             [simple   uint 8          'crc']
         ]
-        ['true','true','true' CEMIFramePollingData
-        ]
         ['true','false','true' CEMIFramePollingDataExt
         ]
     ]
@@ -519,12 +549,31 @@
     ['0x02' IPV4_TCP]
 ]
 
+/*
+ The mode in which the connection should be established:
+ TUNNEL_LINK_LAYER: The gateway assigns a unique KNX address to the client.
+                    The client can then actively participate in communicating
+                    with other KNX devices.
+ TUNNEL_RAW:        The gateway will just pass along the packets and not
+                    automatically generate Ack frames for the packets it
+                    receives for a given client.
+ TUNNEL_BUSMONITOR: The client becomes a passive participant and all frames
+                    on the KNX bus get forwarded to the client. Only one
+                    Busmonitor connection is allowed at any given time.
+*/
 [enum uint 8 'KnxLayer'
     ['0x02' TUNNEL_LINK_LAYER]
     ['0x04' TUNNEL_RAW]
     ['0x80' TUNNEL_BUSMONITOR]
 ]
 
+[enum uint 2 'TPCI'
+    ['0x0' UNNUMBERED_DATA_PACKET]
+    ['0x1' UNNUMBERED]
+    ['0x2' NUMBERED_DATA_PACKET]
+    ['0x3' NUMBERED_CONTROL_DATA]
+]
+
 [enum uint 4 'APCI'
     ['0x0' GROUP_VALUE_READ_PDU]
     ['0x1' GROUP_VALUE_RESPONSE_PDU]
diff --git a/src/site/asciidoc/users/protocols/knxnetip.adoc 
b/src/site/asciidoc/users/protocols/knxnetip.adoc
index 632de18..0a15e16 100644
--- a/src/site/asciidoc/users/protocols/knxnetip.adoc
+++ b/src/site/asciidoc/users/protocols/knxnetip.adoc
@@ -18,3 +18,82 @@
 :icons: font
 
 == KNXnet/IP
+
+=== Connection String Options
+
+[cols="2,2a,5a"]
+|===
+|Name |Value |Description
+
+|Code
+2+|`knxnet-ip`
+
+|Name
+2+|KNXnet/IP Protocol
+
+|Maven Dependency
+2+|
+----
+<dependency>
+  <groupId>org.apache.plc4x</groupId>
+  <artifactId>plc4j-driver-knxnetip</artifactId>
+  <version>{current-last-released-version}</version>
+</dependency>
+----
+
+|Default Transport:
+2+| `udp`
+
+|Compatible Transports:
+2+| - `udp` (Default Port: 3671)
+//- `raw-socket`
+- `pcap-replay`
+
+3+|Supported Operations
+
+|
+| `subscribe`
+|
+
+3+|Options
+
+|
+| `knxproj-file-path`
+| Path to the `knxproj` file. The default KNXnet/IP protocol doesn't provide 
all the information needed to be able to fully decode the messages. For this 
the user needs to provide the project file created in the KNX IDE `ETS5` to 
provide the missing information. Only if this file is provided, will the driver 
be able to decode the data entirely. If this parameter is omitted, only raw KNX 
payload will be returned.
+
+|
+| `group-address-type` (3)
+| KNX Addresses can be encoded in multiple ways. Which encoding is used, is 
too not provided by the protocol itself so it has to be provided externally:
+
+- 3 Levels: {main-group (5 bit)}/{middle-group (3 bit)}/{sub-group (8 bit)}
+- 2 Levels: {main-group (5 bit)}/{sub-group (11 bit)}
+- 1 Level: {sub-group (16 bit)}
+
+The default is 3 levels. If the `knxproj-file-path` this information is 
provided by the file.
+
+|
+| `connection-type`
+| Type of connection used to communicate. Possible values are:
+
+- 'LINK_LAYER' (default): The client becomes a participant of the KNX bus and 
gets it's own individual KNX address.
+- 'RAW': The client gets unmanaged access to the bus (be careful with this)
+- 'BUSMONITOR': The client operates as a busmonitor where he can't actively 
participate on the bus. Only one 'BUSMONITOR' connection is allowed at the same 
time on a KNXnet/IP gateway.
+
+|===
+
+=== Individual Resource Address Format
+
+KNX Addresses usually have one of the following structures:
+
+- 3-level Address: 
`{main-group(0-15)}`/`{middle-group(0-15)}`/`{sub-group(0-255)}`
+- 2-level Address: `{main-group(0-15)}`/`{sub-group(0-4095)}`
+- 1-level Address: `{sub-group(0-65535)}`
+
+Depending on the `group-address-type` configured in the connection string or 
defined in the knxproj-file configured by the `knxproj-file-path` connection 
string parameter, the corresponding address pattern has to be used.
+
+However, each segment allows using of the wildcard character `*`.
+If the addresses used in the KNX installation are structured, this way it is 
possible to, for example (depending on the scheme used):
+
+- Collect all information for a given level of your building: `1/*/*`
+- Collect all information for a given room: `2/4/*`
+- Collect all information about heating in all rooms: `*/*/50`
\ No newline at end of file

Reply via email to