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
