You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2021/01/31 11:20:51 UTC

[plc4x] 39/45: - Added dataIo of the KNX property types - Refactored the Go KNX driver to support the concept of device-connections

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

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

commit be7f1382783ef906917008f51eabfb7e8c5b1697
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Wed Jan 27 21:46:55 2021 +0100

    - Added dataIo of the KNX property types
    - Refactored the Go KNX driver to support the concept of device-connections
---
 .../resources/templates/go/data-io-template.ftlh   |   10 +-
 .../resources/templates/java/data-io-template.ftlh |   22 +-
 plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go |   99 +-
 .../internal/plc4go/knxnetip/KnxNetIpConnection.go | 1055 +++++++++++----
 .../internal/plc4go/knxnetip/KnxNetIpDiscoverer.go |    9 +-
 .../plc4go/knxnetip/KnxNetIpMessageCodec.go        |   18 +-
 plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go  |  367 +----
 .../plc4go/knxnetip/readwrite/model/KnxProperty.go | 1407 ++++++++++++++++++++
 .../resources/protocols/knxnetip/knxnetip.mspec    |  206 +++
 9 files changed, 2540 insertions(+), 653 deletions(-)

diff --git a/build-utils/language-go/src/main/resources/templates/go/data-io-template.ftlh b/build-utils/language-go/src/main/resources/templates/go/data-io-template.ftlh
index ff1d417..db869ae 100644
--- a/build-utils/language-go/src/main/resources/templates/go/data-io-template.ftlh
+++ b/build-utils/language-go/src/main/resources/templates/go/data-io-template.ftlh
@@ -73,7 +73,7 @@ import (
 func ${type.name}Parse(io *utils.ReadBuffer<#if type.parserArguments?has_content>, <#list type.parserArguments as parserArgument>${parserArgument.name} <#if helper.isComplexTypeReference(parserArgument.type) && !helper.isEnumTypeReference(parserArgument.type)>I</#if>${helper.getLanguageTypeNameForTypeReference(parserArgument.type)}<#sep>, </#sep></#list></#if>) (api.PlcValue, error) {
     switch {
     <#list type.switchField.cases as case>
-        case <#if case.discriminatorValues?has_content><#list case.discriminatorValues as discriminatorValue>${helper.toParseExpression(null, type.switchField.discriminatorExpressions[discriminatorValue?index], type.parserArguments)} == <#if helper.discriminatorValueNeedsStringEqualityCheck(type.switchField.discriminatorExpressions[discriminatorValue?index])>"${discriminatorValue}"<#elseif helper.isEnumExpression(discriminatorValue)>${helper.getEnumExpression(discriminatorValue)}<#else>$ [...]
+        <#if case.discriminatorValues?has_content>case <#list case.discriminatorValues as discriminatorValue>${helper.toParseExpression(null, type.switchField.discriminatorExpressions[discriminatorValue?index], type.parserArguments)} == <#if helper.discriminatorValueNeedsStringEqualityCheck(type.switchField.discriminatorExpressions[discriminatorValue?index])>"${discriminatorValue}"<#elseif helper.isEnumExpression(discriminatorValue)>${helper.getEnumExpression(discriminatorValue)}<#else>$ [...]
         <#assign valueDefined=false>
         <#if case.name == "Struct">
             _map := map[string]api.PlcValue{}
@@ -169,8 +169,9 @@ func ${type.name}Parse(io *utils.ReadBuffer<#if type.parserArguments?has_content
 
 func ${type.name}Serialize(io *utils.WriteBuffer, value api.PlcValue<#if type.parserArguments?has_content>, <#list type.parserArguments as parserArgument>${parserArgument.name} <#if helper.isComplexTypeReference(parserArgument.type) && !helper.isEnumTypeReference(parserArgument.type)>I</#if>${helper.getLanguageTypeNameForTypeReference(parserArgument.type)}<#sep>, </#sep></#list></#if>) error {
     switch {
+    <#assign defaultSet=false>
     <#list type.switchField.cases as case>
-        case <#if case.discriminatorValues?has_content><#list case.discriminatorValues as discriminatorValue>${helper.toParseExpression(null, type.switchField.discriminatorExpressions[discriminatorValue?index], type.parserArguments)} == <#if helper.discriminatorValueNeedsStringEqualityCheck(type.switchField.discriminatorExpressions[discriminatorValue?index])>"${discriminatorValue}"<#elseif helper.isEnumExpression(discriminatorValue)>${helper.getEnumExpression(discriminatorValue)}<#else>$ [...]
+        <#if case.discriminatorValues?has_content>case <#list case.discriminatorValues as discriminatorValue>${helper.toParseExpression(null, type.switchField.discriminatorExpressions[discriminatorValue?index], type.parserArguments)} == <#if helper.discriminatorValueNeedsStringEqualityCheck(type.switchField.discriminatorExpressions[discriminatorValue?index])>"${discriminatorValue}"<#elseif helper.isEnumExpression(discriminatorValue)>${helper.getEnumExpression(discriminatorValue)}<#else>$ [...]
         <#list case.fields as field>
             <#switch field.typeName>
                 <#case "array">
@@ -178,6 +179,9 @@ func ${type.name}Serialize(io *utils.WriteBuffer, value api.PlcValue<#if type.pa
 
             // Array Field (${field.name})
             for i := uint32(0); i < uint32(${helper.toSerializationExpression(null, field.loopExpression, type.parserArguments)}); i++ {
+                    <#if case.name = "Struct">
+                ${arrayField.name} := value.GetValue("${arrayField.name}")
+                    </#if>
                 _itemErr := <#if helper.isSimpleTypeReference(arrayField.type)>${helper.getWriteBufferWriteMethodCall(arrayField.type, arrayField.name + ".GetIndex(i).Get" + helper.getLanguageTypeNameForTypeReference(arrayField.type)?cap_first + "()")}<#else>Complex type array in data-io serialization currently not implemented</#if>
                 if _itemErr != nil {
                     return errors.New("Error serializing 'value' field " + _itemErr.Error())
@@ -212,9 +216,11 @@ func ${type.name}Serialize(io *utils.WriteBuffer, value api.PlcValue<#if type.pa
             </#switch>
         </#list>
     </#list>
+    <#if !defaultSet>
         default:
 
             return errors.New("unsupported type")
+    </#if>
     }
     return nil
 }
diff --git a/build-utils/language-java/src/main/resources/templates/java/data-io-template.ftlh b/build-utils/language-java/src/main/resources/templates/java/data-io-template.ftlh
index b0a0cba..a384f6e 100644
--- a/build-utils/language-java/src/main/resources/templates/java/data-io-template.ftlh
+++ b/build-utils/language-java/src/main/resources/templates/java/data-io-template.ftlh
@@ -87,9 +87,9 @@ import java.util.function.Supplier;
 public class ${type.name}IO {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(${type.name}IO.class);
-
     public static PlcValue staticParse(ReadBuffer io<#if type.parserArguments?has_content>, <#list type.parserArguments as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>) throws ParseException {
-        <#list type.switchField.cases as case><#if case.discriminatorValues?has_content>if(<#list case.discriminatorValues as discriminatorValue>EvaluationHelper.equals(${helper.toParseExpression(null, type.switchField.discriminatorExpressions[discriminatorValue?index], type.parserArguments)}, <#if helper.discriminatorValueNeedsStringEqualityCheck(type.switchField.discriminatorExpressions[discriminatorValue?index])>"${discriminatorValue}"<#else>${discriminatorValue}</#if>)<#sep> && </#se [...]
+        <#assign defaultCaseOutput=false>
+        <#list type.switchField.cases as case><#if case.discriminatorValues?has_content>if(<#list case.discriminatorValues as discriminatorValue>EvaluationHelper.equals(${helper.toParseExpression(null, type.switchField.discriminatorExpressions[discriminatorValue?index], type.parserArguments)}, <#if helper.discriminatorValueNeedsStringEqualityCheck(type.switchField.discriminatorExpressions[discriminatorValue?index])>"${discriminatorValue}"<#else>${discriminatorValue}</#if>)<#sep> && </#se [...]
             <#assign valueDefined=false>
             <#list case.fields as field>
                 <#switch field.typeName>
@@ -212,7 +212,9 @@ public class ${type.name}IO {
                 <#-- In this case we need to wrap each field in a PlcValue that matches it's natural type -->
             Map<String, PlcValue> _map = new HashMap<>();
                 <#list case.fields as field>
-                    <#if helper.isPropertyField(field)>
+                    <#if helper.isArrayField(field)>
+            _map.put("${field.name}", new PlcList(${field.name}));
+                    <#elseif helper.isPropertyField(field)>
                         <#switch helper.getLanguageTypeNameForTypeReference(field.type)>
                             <#case "Boolean">
             _map.put("${field.name}", new PlcBOOL(${field.name}));
@@ -297,7 +299,9 @@ public class ${type.name}IO {
                 </#switch>
             </#if>
         }<#sep> else </#sep></#list>
+        <#if !defaultCaseOutput>
         return null;
+        </#if>
     }
 
 <#if outputFlavor != "passive">
@@ -306,7 +310,8 @@ public class ${type.name}IO {
     }
 
     public static WriteBuffer staticSerialize(PlcValue _value<#if type.parserArguments?has_content>, <#list type.parserArguments as parserArgument>${helper.getLanguageTypeNameForTypeReference(parserArgument.type, false)} ${parserArgument.name}<#sep>, </#sep></#list></#if>, boolean littleEndian) throws ParseException {
-        <#list type.switchField.cases as case><#if case.discriminatorValues?has_content>if(<#list case.discriminatorValues as discriminatorValue>EvaluationHelper.equals(${helper.toParseExpression(null, type.switchField.discriminatorExpressions[discriminatorValue?index], type.parserArguments)}, <#if helper.discriminatorValueNeedsStringEqualityCheck(type.switchField.discriminatorExpressions[discriminatorValue?index])>"${discriminatorValue}"<#else>${discriminatorValue}</#if>)<#sep> && </#se [...]
+        <#assign defaultCaseOutput=false>
+        <#list type.switchField.cases as case><#if case.discriminatorValues?has_content>if(<#list case.discriminatorValues as discriminatorValue>EvaluationHelper.equals(${helper.toParseExpression(null, type.switchField.discriminatorExpressions[discriminatorValue?index], type.parserArguments)}, <#if helper.discriminatorValueNeedsStringEqualityCheck(type.switchField.discriminatorExpressions[discriminatorValue?index])>"${discriminatorValue}"<#else>${discriminatorValue}</#if>)<#sep> && </#se [...]
             WriteBuffer io = new WriteBuffer((int) Math.ceil(((float) ${helper.getSizeInBits(case, type.parserArguments)}) / 8.0f), littleEndian);
 
             <#list case.fields as field>
@@ -314,10 +319,17 @@ public class ${type.name}IO {
                     <#case "array">
             PlcList values = (PlcList) _value;
 
+                        <#if case.name == "Struct">
+            for (PlcValue val : ((List<PlcValue>) values.getStruct().get("${field.name}").getList())) {
+                ${helper.getLanguageTypeNameForField(field)} value = (${helper.getLanguageTypeNameForField(field)}) val.get${helper.getLanguageTypeNameForField(field)?cap_first}();
+                ${helper.getWriteBufferWriteMethodCall(field.type, "value")};
+            }
+                        <#else>
             for (PlcValue val : ((List<PlcValue>) values.getList())) {
                 ${helper.getLanguageTypeNameForField(field)} value = (${helper.getLanguageTypeNameForField(field)}) val.get${helper.getLanguageTypeNameForField(field)?cap_first}();
                 ${helper.getWriteBufferWriteMethodCall(field.type, "(" + field.name + ")")};
             }
+                        </#if>
 
                     <#if case.name == "BOOL">
             while (io.getPos() < io.getData().length) {
@@ -364,7 +376,9 @@ public class ${type.name}IO {
             </#list>
             return io;
         }<#sep> else </#sep></#list>
+        <#if !defaultCaseOutput>
         return null;
+        </#if>
     }
 </#if>
 
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go
index 9244cc2..0f38f8c 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go
@@ -19,14 +19,13 @@
 package knxnetip
 
 import (
-	"fmt"
-	driverModel "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
-	"github.com/apache/plc4x/plc4go/internal/plc4go/spi"
-	"github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
-	apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
-	"strconv"
-	"strings"
-	"time"
+    "fmt"
+    "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+    "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+    apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+    "strconv"
+    "strings"
+    "time"
 )
 
 type KnxNetIpBrowser struct {
@@ -47,13 +46,10 @@ func NewKnxNetIpBrowser(connection *KnxNetIpConnection, messageCodec spi.Message
 func (b KnxNetIpBrowser) Browse(browseRequest apiModel.PlcBrowseRequest) <-chan apiModel.PlcBrowseRequestResult {
 	result := make(chan apiModel.PlcBrowseRequestResult)
 	sendResult := func(browseResponse apiModel.PlcBrowseResponse, err error) {
-		select {
-		case result <- apiModel.PlcBrowseRequestResult{
+		result <- apiModel.PlcBrowseRequestResult{
 			Request:  browseRequest,
 			Response: browseResponse,
 			Err:      err,
-		}:
-		default:
 		}
 	}
 
@@ -88,70 +84,23 @@ func (b KnxNetIpBrowser) Browse(browseRequest apiModel.PlcBrowseRequest) <-chan
 				//   - If an object-id is provided, check if this object id exists
 				//   - If a property-id is provided, check if this property exists and try to get more information about it
 				case KnxNetIpDevicePropertyAddressPlcField:
-					individualAddress := field.(KnxNetIpDevicePropertyAddressPlcField)
-					sourceAddress := &driverModel.KnxAddress{
-						MainGroup:   0,
-						MiddleGroup: 0,
-						SubGroup:    0,
-					}
-
-					// Serialize the target address to a 2-byte value
-					targetAddress := make([]int8, 2)
-					main, _ := strconv.Atoi(individualAddress.MainGroup)
-					middle, _ := strconv.Atoi(individualAddress.MiddleGroup)
-					sub, _ := strconv.Atoi(individualAddress.SubGroup)
-					targetAddress[0] = int8((main&0xF)<<4 | (middle & 0xF))
-					targetAddress[1] = int8(sub)
-
-					curSequenceCounter := b.sequenceCounter
-					b.sequenceCounter++
-					deviceConnectionRequest := driverModel.NewTunnelingRequest(
-						driverModel.NewTunnelingRequestDataBlock(
-							b.connection.CommunicationChannelId,
-							curSequenceCounter),
-						driverModel.NewLDataReq(0, nil,
-							driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
-								sourceAddress, targetAddress,
-								driverModel.NewApduControlContainer(driverModel.NewApduControlConnect(), 0, false, 0),
-								true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
-
-					// Send the request
-					done := make(chan bool)
-					err = b.connection.SendRequest(
-						deviceConnectionRequest,
-						// The Gateway is now supposed to send an Ack to this request.
-						func(message interface{}) bool {
-							tunnelingRequest := driverModel.CastTunnelingRequest(message)
-							if tunnelingRequest == nil ||
-							    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != b.connection.CommunicationChannelId {
-								return false
-							}
-							lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-							return lDataCon != nil
-						},
-						func(message interface{}) error {
-							tunnelingRequest := driverModel.CastTunnelingRequest(message)
-							lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-							// If the error flag is not set, we've found a device
-							if !lDataCon.DataFrame.ErrorFlag {
-								queryResult := apiModel.PlcBrowseQueryResult{
-									Address: fmt.Sprintf("%s.%s.%s",
-										individualAddress.MainGroup,
-										individualAddress.MiddleGroup,
-										individualAddress.SubGroup),
-									PossibleDataTypes: nil,
-								}
-								queryResults = append(queryResults, queryResult)
-							}
-							done <- true
-							return nil
-						},
-						time.Second*1)
-					select {
-					case <-done:
-					case <-time.After(time.Second * 2):
-					}
+                    targetAddress := FieldToKnxAddress(field.(KnxNetIpDevicePropertyAddressPlcField))
 
+					// Send a connection request to the device
+					deviceConnections := b.connection.ConnectToDevice(*targetAddress)
+                    select {
+                    case deviceConnection := <-deviceConnections:
+                        if deviceConnection != nil {
+                            queryResult := apiModel.PlcBrowseQueryResult{
+                                Address: fmt.Sprintf("%d.%d.%d",
+                                    targetAddress.MainGroup,
+                                    targetAddress.MiddleGroup,
+                                    targetAddress.SubGroup),
+                                PossibleDataTypes: nil,
+                            }
+                            queryResults = append(queryResults, queryResult)
+                        }
+                    }
 					// Just to slow things down a bit (This way we can't exceed the max number of requests per minute)
 					time.Sleep(time.Millisecond * 20)
 				// - A Group Address
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go
index 0f77842..5bdd243 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go
@@ -31,6 +31,7 @@ import (
     "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
     "github.com/apache/plc4x/plc4go/pkg/plc4go"
     apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+    "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
     "math"
     "net"
     "strconv"
@@ -88,6 +89,12 @@ func (m ConnectionMetadata) CanBrowse() bool {
     return true
 }
 
+type KnxDeviceConnection struct {
+    counter          uint8
+    deviceDescriptor uint16
+    maxApdu          uint16
+}
+
 type KnxNetIpConnection struct {
     messageCodec             spi.MessageCodec
     options                  map[string][]string
@@ -103,12 +110,14 @@ type KnxNetIpConnection struct {
     valueCache      map[uint16][]int8
     valueCacheMutex sync.RWMutex
     metadata        *ConnectionMetadata
+    defaultTtl      time.Duration
 
     GatewayKnxAddress             *driverModel.KnxAddress
     ClientKnxAddress              *driverModel.KnxAddress
     CommunicationChannelId        uint8
     SequenceCounter               int32
     TunnelingRequestExpectationId int32
+    DeviceConnections             map[driverModel.KnxAddress]*KnxDeviceConnection
 
     requestInterceptor internalModel.RequestInterceptor
     plc4go.PlcConnection
@@ -119,6 +128,11 @@ type InternalKnxNetIpConnection interface {
     SendRequest(request *driverModel.KnxNetIpMessage, expect func(response interface{}) (bool, bool)) (int32, chan interface{})
 }
 
+type KnxReadResult struct {
+    returnCode apiModel.PlcResponseCode
+    value      *values.PlcValue
+}
+
 func NewKnxNetIpConnection(transportInstance transports.TransportInstance, options map[string][]string, fieldHandler spi.PlcFieldHandler) *KnxNetIpConnection {
     connection := &KnxNetIpConnection{
         options:            options,
@@ -132,6 +146,8 @@ func NewKnxNetIpConnection(transportInstance transports.TransportInstance, optio
         valueCache:         map[uint16][]int8{},
         valueCacheMutex:    sync.RWMutex{},
         metadata:           &ConnectionMetadata{},
+        defaultTtl:         time.Millisecond * 5000,
+        DeviceConnections:  map[driverModel.KnxAddress]*KnxDeviceConnection{},
     }
     connection.messageCodec = NewKnxNetIpMessageCodec(transportInstance, connection.interceptIncomingMessage)
     return connection
@@ -142,7 +158,6 @@ func (m *KnxNetIpConnection) Connect() <-chan plc4go.PlcConnectionConnectResult
     sendResult := func(connection plc4go.PlcConnection, err error) {
         select {
         case result <- plc4go.NewPlcConnectionConnectResult(connection, err):
-        default:
         }
     }
 
@@ -153,177 +168,168 @@ func (m *KnxNetIpConnection) Connect() <-chan plc4go.PlcConnectionConnectResult
             return
         }
 
-        transportInstanceExposer, ok := m.messageCodec.(spi.TransportInstanceExposer)
-        if !ok {
-            sendResult(m, errors.New(
-                "used transport, is not a TransportInstanceExposer"))
-            return
-        }
-
-        // Prepare a SearchReq
-        udpTransportInstance, ok := transportInstanceExposer.GetTransportInstance().(*udp.UdpTransportInstance)
-        if !ok {
-            sendResult(m, errors.New(
-                "used transport, is not a UdpTransportInstance"))
-            return
-        }
-        localAddress := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(udpTransportInstance.LocalAddress.IP))
-        discoveryEndpoint := driverModel.NewHPAIDiscoveryEndpoint(
-            driverModel.HostProtocolCode_IPV4_UDP, localAddress, uint16(udpTransportInstance.LocalAddress.Port))
-        searchRequest := driverModel.NewSearchRequest(discoveryEndpoint)
-
-        // Send the SearchReq
-        err = m.messageCodec.SendRequest(
-            searchRequest,
-            func(message interface{}) bool {
-                searchResponse := driverModel.CastSearchResponse(message)
-                return searchResponse != nil
-            },
-            func(message interface{}) error {
-                searchResponse := driverModel.CastSearchResponse(message)
-
-                // Save some important information
-                m.metadata.KnxMedium = searchResponse.DibDeviceInfo.KnxMedium
-                m.metadata.GatewayName = string(bytes.Trim(utils.Int8ArrayToByteArray(
-                    searchResponse.DibDeviceInfo.DeviceFriendlyName), "\x00"))
-                m.GatewayKnxAddress = searchResponse.DibDeviceInfo.KnxAddress
-                m.metadata.GatewayKnxAddress = KnxAddressToString(m.GatewayKnxAddress)
-                m.metadata.ProjectNumber = searchResponse.DibDeviceInfo.ProjectInstallationIdentifier.ProjectNumber
-                m.metadata.InstallationNumber = searchResponse.DibDeviceInfo.ProjectInstallationIdentifier.InstallationNumber
-                m.metadata.DeviceSerialNumber = searchResponse.DibDeviceInfo.KnxNetIpDeviceSerialNumber
-                m.metadata.DeviceMulticastAddress = searchResponse.DibDeviceInfo.KnxNetIpDeviceMulticastAddress.Addr
-                m.metadata.DeviceMacAddress = searchResponse.DibDeviceInfo.KnxNetIpDeviceMacAddress.Addr
-                m.metadata.SupportedServices = []string{}
-                supportsTunneling := false
-                for _, serviceId := range searchResponse.DibSuppSvcFamilies.ServiceIds {
-                    m.metadata.SupportedServices = append(m.metadata.SupportedServices, serviceId.Child.GetTypeName())
-                    // If this is an instance of the "tunneling", service, this connection supports tunneling
-                    _, ok := serviceId.Child.(*driverModel.KnxNetIpTunneling)
-                    if ok {
-                        supportsTunneling = true
-                        break
-                    }
+        searchResponseChannel := m.sendGatewaySearchRequest()
+        select {
+        case searchResponse := <-searchResponseChannel:
+            // Save some important information
+            m.metadata.KnxMedium = searchResponse.DibDeviceInfo.KnxMedium
+            m.metadata.GatewayName = string(bytes.Trim(utils.Int8ArrayToByteArray(
+                searchResponse.DibDeviceInfo.DeviceFriendlyName), "\x00"))
+            m.GatewayKnxAddress = searchResponse.DibDeviceInfo.KnxAddress
+            m.metadata.GatewayKnxAddress = KnxAddressToString(m.GatewayKnxAddress)
+            m.metadata.ProjectNumber = searchResponse.DibDeviceInfo.ProjectInstallationIdentifier.ProjectNumber
+            m.metadata.InstallationNumber = searchResponse.DibDeviceInfo.ProjectInstallationIdentifier.InstallationNumber
+            m.metadata.DeviceSerialNumber = searchResponse.DibDeviceInfo.KnxNetIpDeviceSerialNumber
+            m.metadata.DeviceMulticastAddress = searchResponse.DibDeviceInfo.KnxNetIpDeviceMulticastAddress.Addr
+            m.metadata.DeviceMacAddress = searchResponse.DibDeviceInfo.KnxNetIpDeviceMacAddress.Addr
+            m.metadata.SupportedServices = []string{}
+            supportsTunneling := false
+            for _, serviceId := range searchResponse.DibSuppSvcFamilies.ServiceIds {
+                m.metadata.SupportedServices = append(m.metadata.SupportedServices, serviceId.Child.GetTypeName())
+                // If this is an instance of the "tunneling", service, this connection supports tunneling
+                _, ok := serviceId.Child.(*driverModel.KnxNetIpTunneling)
+                if ok {
+                    supportsTunneling = true
+                    break
                 }
+            }
 
-                // If the current device supports tunneling, create a tunneling connection.
-                // Via this connection we then get access to the entire KNX network this Gateway is connected to.
-                if supportsTunneling {
-                    // As soon as we got a successful search-response back, send a connection request.
-                    localAddress := m.castIpToKnxAddress(udpTransportInstance.LocalAddress.IP)
-                    connectionRequest := driverModel.NewConnectionRequest(
-                        driverModel.NewHPAIDiscoveryEndpoint(driverModel.HostProtocolCode_IPV4_UDP,
-                            localAddress, uint16(udpTransportInstance.LocalAddress.Port)),
-                        driverModel.NewHPAIDataEndpoint(driverModel.HostProtocolCode_IPV4_UDP,
-                            localAddress, uint16(udpTransportInstance.LocalAddress.Port)),
-                        driverModel.NewConnectionRequestInformationTunnelConnection(driverModel.KnxLayer_TUNNEL_LINK_LAYER),
-                    )
-
-                    // Send the connection request
-                    err = m.messageCodec.SendRequest(
-                        connectionRequest,
-                        func(message interface{}) bool {
-                            connectionResponse := driverModel.CastConnectionResponse(message)
-                            return connectionResponse != nil
-                        },
-                        func(message interface{}) error {
-                            connectionResponse := driverModel.CastConnectionResponse(message)
-
-                            // Save the communication channel id
-                            m.CommunicationChannelId = connectionResponse.CommunicationChannelId
-
-                            // Reset the sequence counter
-                            m.SequenceCounter = -1
-
-                            // If the connection was successful, the gateway will now forward any packets
-                            // on the KNX bus that are broadcast packets to us, so we have to setup things
-                            // to handle these incoming messages.
-                            if connectionResponse.Status == driverModel.Status_NO_ERROR {
-                                go func() {
-                                    defaultIncomingMessageChannel := m.messageCodec.GetDefaultIncomingMessageChannel()
-                                    for {
-                                        incomingMessage := <-defaultIncomingMessageChannel
-                                        tunnelingRequest := driverModel.CastTunnelingRequest(incomingMessage)
-                                        if tunnelingRequest == nil {
-                                            tunnelingResponse := driverModel.CastTunnelingResponse(incomingMessage)
-                                            if tunnelingResponse != nil {
-                                                fmt.Printf("Got an unhandled TunnelingResponse message %v\n", tunnelingResponse)
-                                            } else {
-                                                fmt.Printf("Not a TunnelingRequest message %v\n", incomingMessage)
-                                            }
+            // If the current device supports tunneling, create a tunneling connection.
+            // Via this connection we then get access to the entire KNX network this Gateway is connected to.
+            if supportsTunneling {
+                // As soon as we got a successful search-response back, send a connection request.
+                connectionResponseChannel := m.sendGatewayConnectionRequest()
+                select {
+                case connectionResponse := <-connectionResponseChannel:
+                    // Save the communication channel id
+                    m.CommunicationChannelId = connectionResponse.CommunicationChannelId
+
+                    // Reset the sequence counter
+                    m.SequenceCounter = -1
+
+                    // If the connection was successful, the gateway will now forward any packets
+                    // on the KNX bus that are broadcast packets to us, so we have to setup things
+                    // to handle these incoming messages.
+                    switch connectionResponse.Status {
+                    case driverModel.Status_NO_ERROR:
+                        // Save the KNX Address the Gateway assigned to us for this connection.
+                        tunnelConnectionDataBlock :=
+                            driverModel.CastConnectionResponseDataBlockTunnelConnection(
+                                connectionResponse.ConnectionResponseDataBlock)
+                        m.ClientKnxAddress = tunnelConnectionDataBlock.KnxAddress
+
+                        // Start a timer that sends connection-state requests every 60 seconds
+                        m.connectionStateTimer = time.NewTicker(60 * time.Second)
+                        m.quitConnectionStateTimer = make(chan struct{})
+                        go func() {
+                            for {
+                                select {
+                                case <-m.connectionStateTimer.C:
+                                    // We're using the connection-state-request as ping operation ...
+                                    ping := m.Ping()
+                                    select {
+                                    case pingResult := <-ping:
+                                        if pingResult.Err != nil {
+                                            // TODO: Do some error handling here ...
+                                            m.connectionStateTimer.Stop()
+                                        }
+                                    case <-time.After(5 * time.Second):
+                                        // Close the connection
+                                        m.Close()
+                                    }
+
+                                // If externally a request to stop the timer was issued, stop the timer.
+                                case <-m.quitConnectionStateTimer:
+                                    // TODO: Do some error handling here ...
+                                    m.connectionStateTimer.Stop()
+                                    return
+                                }
+                            }
+                        }()
+
+                        // Create a go routine to handle incoming tunneling-requests which haven't been
+                        // handled by any other handler. This is where usually the GroupValueWrite messages
+                        // are being handled.
+                        go func() {
+                            defaultIncomingMessageChannel := m.messageCodec.GetDefaultIncomingMessageChannel()
+                            for {
+                                incomingMessage := <-defaultIncomingMessageChannel
+                                tunnelingRequest := driverModel.CastTunnelingRequest(incomingMessage)
+                                if tunnelingRequest == nil {
+                                    tunnelingResponse := driverModel.CastTunnelingResponse(incomingMessage)
+                                    if tunnelingResponse != nil {
+                                        fmt.Printf("Got an unhandled TunnelingResponse message %v\n", tunnelingResponse)
+                                    } else {
+                                        fmt.Printf("Not a TunnelingRequest message %v\n", incomingMessage)
+                                    }
+                                } else {
+                                    if tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+                                        fmt.Printf("Not for this connection %v\n", tunnelingRequest)
+                                        continue
+                                    }
+
+                                    lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+                                    if lDataInd != nil {
+                                        // Get APDU, source and target address
+                                        var apdu *driverModel.Apdu
+                                        var sourceAddress *driverModel.KnxAddress
+                                        //var targetAddress []int8
+                                        lDataFrameData := driverModel.CastLDataFrameData(lDataInd.DataFrame)
+                                        if lDataFrameData != nil {
+                                            apdu = lDataFrameData.Apdu
+                                            sourceAddress = lDataFrameData.SourceAddress
+                                            //targetAddress = lDataFrameData.DestinationAddress
                                         } else {
-                                            if tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
-                                                fmt.Printf("Not for this connection %v\n", tunnelingRequest)
-                                                continue
-                                            }
-                                            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
-                                            if lDataInd != nil {
-                                                m.handleIncomingTunnelingRequest(tunnelingRequest)
+                                            lDataFrameDataExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+                                            if lDataFrameDataExt != nil {
+                                                apdu = lDataFrameDataExt.Apdu
+                                                sourceAddress = lDataFrameDataExt.SourceAddress
+                                                //targetAddress = lDataFrameDataExt.DestinationAddress
                                             }
                                         }
-                                    }
-                                }()
-
-                                // Save the KNX Address the Gateway assigned to us for this connection.
-                                tunnelConnectionDataBlock :=
-                                    driverModel.CastConnectionResponseDataBlockTunnelConnection(
-                                        connectionResponse.ConnectionResponseDataBlock)
-                                m.ClientKnxAddress = tunnelConnectionDataBlock.KnxAddress
-
-                                // Fire the "connected" event
-                                sendResult(m, nil)
-
-                                // Start a timer that sends connection-state requests every 60 seconds
-                                m.connectionStateTimer = time.NewTicker(60 * time.Second)
-                                m.quitConnectionStateTimer = make(chan struct{})
-                                go func() {
-                                    for {
-                                        select {
-                                        case <-m.connectionStateTimer.C:
-                                            // We're using the connection-state-request as ping operation ...
-                                            ping := m.Ping()
-                                            select {
-                                            case pingResult := <-ping:
-                                                if pingResult.Err != nil {
-                                                    // TODO: Do some error handling here ...
-                                                    m.connectionStateTimer.Stop()
+
+                                        // If this is not an APDU, there is no need to further handle it.
+                                        if apdu == nil {
+                                            continue
+                                        }
+
+                                        // If this is an incoming disconnect request, remove the device
+                                        // from the device connections, otherwise handle it as normal
+                                        // incoming message.
+                                        apduControlContainer := driverModel.CastApduControlContainer(apdu)
+                                        if apduControlContainer != nil {
+                                            disconnectApdu := driverModel.CastApduControlDisconnect(apduControlContainer.ControlApdu)
+                                            if disconnectApdu != nil {
+                                                if m.DeviceConnections[*sourceAddress] != nil /* && m.ClientKnxAddress == Int8ArrayToKnxAddress(targetAddress)*/ {
+                                                    // Remove the connection
+                                                    delete(m.DeviceConnections, *sourceAddress)
                                                 }
-                                            case <-time.After(5 * time.Second):
-                                                // Close the connection
-                                                m.Close()
                                             }
-
-                                        // If externally a request to stop the timer was issued, stop the timer.
-                                        case <-m.quitConnectionStateTimer:
-                                            // TODO: Do some error handling here ...
-                                            m.connectionStateTimer.Stop()
-                                            return
+                                        } else {
+                                            m.handleIncomingTunnelingRequest(tunnelingRequest)
                                         }
                                     }
-                                }()
+                                }
                             }
-                            return nil
-                        },
-                        time.Second*1)
-                } else {
-                    return errors.New("current device doesn't support tunneling")
+                        }()
+
+                        // Fire the "connected" event
+                        sendResult(m, nil)
+                    case driverModel.Status_NO_MORE_CONNECTIONS:
+                        sendResult(nil, errors.New("no more connections"))
+                    }
                 }
-                return nil
-            },
-            time.Second*1)
-        if err != nil {
-            sendResult(nil, err)
+            }
         }
     }()
     return result
 }
 
 func (m *KnxNetIpConnection) Close() <-chan plc4go.PlcConnectionCloseResult {
-    // TODO: Implement ...
     result := make(chan plc4go.PlcConnectionCloseResult)
     sendResult := func(connection plc4go.PlcConnection, err error) {
         select {
         case result <- plc4go.NewPlcConnectionCloseResult(connection, err):
-        default:
         }
     }
 
@@ -331,49 +337,24 @@ func (m *KnxNetIpConnection) Close() <-chan plc4go.PlcConnectionCloseResult {
         // Stop the connection-state checker.
         m.connectionStateTimer.Stop()
 
-        transportInstanceExposer, ok := m.messageCodec.(spi.TransportInstanceExposer)
-        if !ok {
-            sendResult(nil, errors.New(
-                "used transport, is not a TransportInstanceExposer"))
-            return
-        }
-
-        // Prepare a SearchReq
-        udpTransportInstance, ok := transportInstanceExposer.GetTransportInstance().(*udp.UdpTransportInstance)
-        if !ok {
-            sendResult(nil, errors.New(
-                "used transport, is not a UdpTransportInstance"))
-            return
+        // Disconnect from all knx devices we are still connected to.
+        for address := range m.DeviceConnections {
+            disconnects := m.sendDeviceDisconnectionRequest(address)
+            select {
+            case _ = <-disconnects:
+                delete(m.DeviceConnections, address)
+            }
         }
 
-        localAddress := m.castIpToKnxAddress(udpTransportInstance.LocalAddress.IP)
-
-        disconnectRequest := driverModel.NewDisconnectRequest(
-            m.CommunicationChannelId,
-            driverModel.NewHPAIControlEndpoint(
-                driverModel.HostProtocolCode_IPV4_UDP,
-                localAddress,
-                uint16(udpTransportInstance.LocalAddress.Port)))
-
-        err := m.SendRequest(
-            disconnectRequest,
-            func(message interface{}) bool {
-                disconnectResponse := driverModel.CastDisconnectResponse(message)
-                return disconnectResponse != nil
-            },
-            func(message interface{}) error {
-                disconnectResponse := driverModel.CastDisconnectResponse(message)
-                if disconnectResponse.Status == driverModel.Status_NO_ERROR {
-                    sendResult(m, nil)
-                } else {
-                    sendResult(m, errors.New("got an unexpected response for disconnect "+disconnectResponse.Status.String()))
-                }
-                return nil
-            },
-            time.Second*5)
-
-        if err != nil {
-            sendResult(nil, err)
+        // Send a disconnect request from the gateway.
+        disconnectionResponseChannel := m.sendGatewayDisconnectionRequest()
+        select {
+        case disconnectResponse := <-disconnectionResponseChannel:
+            if disconnectResponse.Status == driverModel.Status_NO_ERROR {
+                sendResult(m, nil)
+            } else {
+                sendResult(m, errors.New("got an unexpected response for disconnect "+disconnectResponse.Status.String()))
+            }
         }
     }()
     return result
@@ -397,60 +378,105 @@ func (m *KnxNetIpConnection) Ping() <-chan plc4go.PlcConnectionPingResult {
     sendResult := func(err error) {
         select {
         case result <- plc4go.NewPlcConnectionPingResult(err):
-        default:
         }
     }
 
-    //	diagnosticRequestPdu := driverModel.NewModbusPDUDiagnosticRequest(0, 0x42)
     go func() {
-        transportInstanceExposer, ok := m.messageCodec.(spi.TransportInstanceExposer)
-        if !ok {
-            sendResult(errors.New(
-                "used transport, is not a TransportInstanceExposer"))
-            return
+        // Send the connection state request
+        connectionStateResponseChannel := m.sendConnectionStateRequest()
+        select {
+        case connectionStateResponse := <-connectionStateResponseChannel:
+            if connectionStateResponse.Status != driverModel.Status_NO_ERROR {
+                sendResult(errors.New("got a failure response code " + strconv.Itoa(int(connectionStateResponse.Status))))
+            } else {
+                sendResult(nil)
+            }
         }
+        return
+    }()
+    return result
+}
 
-        // Prepare a SearchReq
-        udpTransportInstance, ok := transportInstanceExposer.GetTransportInstance().(*udp.UdpTransportInstance)
-        if !ok {
-            sendResult(errors.New(
-                "used transport, is not a UdpTransportInstance"))
-            return
+func (m *KnxNetIpConnection) ConnectToDevice(targetAddress driverModel.KnxAddress) <-chan *KnxDeviceConnection {
+    result := make(chan *KnxDeviceConnection)
+
+    // If we're already connected, use that connection instead.
+    if connection, ok := m.DeviceConnections[targetAddress]; ok {
+        result <- connection
+        return result
+    }
+
+    // First send a connection request
+    go func() {
+        controlConnectResponseChannel := m.sendDeviceConnectionRequest(targetAddress)
+        select {
+        case _ = <-controlConnectResponseChannel:
+            // If the connection request was successful, try to read the device-descriptor
+            deviceDescriptorResponses := m.sendDeviceDeviceDescriptorReadRequest(targetAddress)
+            select {
+            case _ = <-deviceDescriptorResponses:
+                // Last, not least, read the max APDU size
+                propertyValueResponses := m.sendDevicePropertyReadRequest(targetAddress, 0, 56)
+                select {
+                case propertyValueResponse := <-propertyValueResponses:
+                    // If we were able to read the max APDU size, then use the minimum of
+                    // the connection APDU size and the device APDU size, otherwise use the
+                    // default APDU Size
+                    deviceApduSize := uint16(100)
+                    if propertyValueResponse.returnCode == apiModel.PlcResponseCode_OK {
+                        deviceApduSize = (*propertyValueResponse.value).GetUint16()
+                    }
+                    connection := m.DeviceConnections[targetAddress]
+                    connection.maxApdu = uint16(math.Min(float64(deviceApduSize), 240))
+                    result <- connection
+                }
+            }
         }
+    }()
 
-        localAddress := m.castIpToKnxAddress(udpTransportInstance.LocalAddress.IP)
+    return result
+}
 
-        connectionStateRequest := driverModel.NewConnectionStateRequest(
-            m.CommunicationChannelId,
-            driverModel.NewHPAIControlEndpoint(
-                driverModel.HostProtocolCode_IPV4_UDP,
-                localAddress, uint16(udpTransportInstance.LocalAddress.Port)))
+func (m *KnxNetIpConnection) DisconnectFromDevice(targetAddress driverModel.KnxAddress) <-chan *KnxDeviceConnection {
+    result := make(chan *KnxDeviceConnection)
 
-        // Send the connection state request
-        err := m.messageCodec.SendRequest(
-            connectionStateRequest,
-            func(message interface{}) bool {
-                connectionStateResponse := driverModel.CastConnectionStateResponse(message)
-                return connectionStateResponse != nil
-            },
-            func(message interface{}) error {
-                connectionStateResponse := driverModel.CastConnectionStateResponse(message)
-                if connectionStateResponse.Status != driverModel.Status_NO_ERROR {
-                    sendResult(errors.New("got a failure response code " + strconv.Itoa(int(connectionStateResponse.Status))))
-                } else {
-                    sendResult(nil)
-                }
-                return nil
-            },
-            // According to the KNX spec, the client is required to wait 10 seconds and if this doesn't
-            // come in within this time to re-try 3 times (total of 4 times)
-            // TODO: Implement the logic to re-try 3 times
-            time.Second*10)
-        if err != nil {
-            sendResult(err)
+    if connection, ok := m.DeviceConnections[targetAddress]; ok {
+        disconnects := m.sendDeviceDisconnectionRequest(targetAddress)
+        select {
+        case _ = <-disconnects:
+            result <- connection
+        }
+    } else {
+        result <- nil
+    }
+
+    return result
+}
+
+func (m *KnxNetIpConnection) ReadDeviceProperty(targetAddress driverModel.KnxAddress, objectId uint8, propertyId uint8) <-chan KnxReadResult {
+    result := make(chan KnxReadResult)
+
+    go func() {
+        // Check if there is already a connection available,
+        // if not, create a new one.
+        connection, ok := m.DeviceConnections[targetAddress]
+        if !ok {
+            connections := m.ConnectToDevice(targetAddress)
+            select {
+            case connection = <-connections:
+            }
+        }
+
+        // If we successfully got a connection, read the property
+        if connection != nil {
+            propertyValueResponses := m.sendDevicePropertyReadRequest(targetAddress, objectId, propertyId)
+            select {
+            case propertyValueResponse := <-propertyValueResponses:
+                result <- propertyValueResponse
+            }
         }
-        return
     }()
+
     return result
 }
 
@@ -497,7 +523,7 @@ func (m *KnxNetIpConnection) GetPlcValueHandler() spi.PlcValueHandler {
     return m.valueHandler
 }
 
-func (m *KnxNetIpConnection) Send(request *driverModel.KnxNetIpMessage) error {
+func (m *KnxNetIpConnection) send(request *driverModel.KnxNetIpMessage) error {
     // If this is a tunneling request, we need to update the communicationChannelId and assign a sequenceCounter
     tunnelingRequest := driverModel.CastTunnelingRequest(request)
     if tunnelingRequest != nil {
@@ -507,7 +533,549 @@ func (m *KnxNetIpConnection) Send(request *driverModel.KnxNetIpMessage) error {
     return m.messageCodec.Send(request)
 }
 
-func (m *KnxNetIpConnection) SendRequest(request *driverModel.KnxNetIpMessage, acceptsMessage spi.AcceptsMessage, handleMessage spi.HandleMessage, ttl time.Duration) error {
+func (m *KnxNetIpConnection) sendGatewaySearchRequest() chan driverModel.SearchResponse {
+    result := make(chan driverModel.SearchResponse)
+    localAddress, err := m.getLocalAddress()
+    if err != nil {
+        close(result)
+        return result
+    }
+    localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP))
+    discoveryEndpoint := driverModel.NewHPAIDiscoveryEndpoint(
+        driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port))
+    searchRequest := driverModel.NewSearchRequest(discoveryEndpoint)
+    err = m.messageCodec.SendRequest(searchRequest,
+        func(message interface{}) bool {
+            searchResponse := driverModel.CastSearchResponse(message)
+            return searchResponse != nil
+        },
+        func(message interface{}) error {
+            searchResponse := driverModel.CastSearchResponse(message)
+            result <- *searchResponse
+            close(result)
+            return nil
+        },
+        m.defaultTtl)
+    if err != nil {
+        close(result)
+    }
+    return result
+}
+
+func (m *KnxNetIpConnection) sendGatewayConnectionRequest() chan driverModel.ConnectionResponse {
+    result := make(chan driverModel.ConnectionResponse)
+    localAddress, err := m.getLocalAddress()
+    if err != nil {
+        close(result)
+        return result
+    }
+    localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP)[len(localAddress.IP)-4:])
+    connectionRequest := driverModel.NewConnectionRequest(
+        driverModel.NewHPAIDiscoveryEndpoint(driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port)),
+        driverModel.NewHPAIDataEndpoint(driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port)),
+        driverModel.NewConnectionRequestInformationTunnelConnection(driverModel.KnxLayer_TUNNEL_LINK_LAYER),
+    )
+    err = m.messageCodec.SendRequest(connectionRequest,
+        func(message interface{}) bool {
+            connectionResponse := driverModel.CastConnectionResponse(message)
+            return connectionResponse != nil
+        },
+        func(message interface{}) error {
+            connectionResponse := driverModel.CastConnectionResponse(message)
+            result <- *connectionResponse
+            close(result)
+            return nil
+        },
+        m.defaultTtl)
+    if err != nil {
+        close(result)
+    }
+    return result
+}
+
+func (m *KnxNetIpConnection) sendGatewayDisconnectionRequest() chan driverModel.DisconnectResponse {
+    result := make(chan driverModel.DisconnectResponse)
+    localAddress, err := m.getLocalAddress()
+    if err != nil {
+        close(result)
+        return result
+    }
+    localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP)[len(localAddress.IP)-4:])
+    disconnectRequest := driverModel.NewDisconnectRequest(
+        m.CommunicationChannelId,
+        driverModel.NewHPAIControlEndpoint(
+            driverModel.HostProtocolCode_IPV4_UDP,
+            localAddr,
+            uint16(localAddress.Port)))
+    err = m.messageCodec.SendRequest(disconnectRequest,
+        func(message interface{}) bool {
+            disconnectResponse := driverModel.CastDisconnectResponse(message)
+            return disconnectResponse != nil
+        },
+        func(message interface{}) error {
+            disconnectResponse := driverModel.CastDisconnectResponse(message)
+            result <- *disconnectResponse
+            close(result)
+            return nil
+        },
+        m.defaultTtl)
+    if err != nil {
+        close(result)
+    }
+    return result
+}
+
+func (m *KnxNetIpConnection) sendConnectionStateRequest() chan driverModel.ConnectionStateResponse {
+    result := make(chan driverModel.ConnectionStateResponse)
+    localAddress, err := m.getLocalAddress()
+    if err != nil {
+        close(result)
+        return result
+    }
+    localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP)[len(localAddress.IP)-4:])
+    connectionStateRequest := driverModel.NewConnectionStateRequest(
+        m.CommunicationChannelId,
+        driverModel.NewHPAIControlEndpoint(
+            driverModel.HostProtocolCode_IPV4_UDP,
+            localAddr, uint16(localAddress.Port)))
+    err = m.messageCodec.SendRequest(connectionStateRequest,
+        func(message interface{}) bool {
+            connectionStateResponse := driverModel.CastConnectionStateResponse(message)
+            return connectionStateResponse != nil
+        },
+        func(message interface{}) error {
+            connectionStateResponse := driverModel.CastConnectionStateResponse(message)
+            result <- *connectionStateResponse
+            close(result)
+            return nil
+        },
+        m.defaultTtl)
+    if err != nil {
+        close(result)
+    }
+    return result
+}
+
+func (m *KnxNetIpConnection) sendDeviceConnectionRequest(targetAddress driverModel.KnxAddress) chan driverModel.ApduControlConnect {
+    result := make(chan driverModel.ApduControlConnect)
+
+    go func() {
+        // Send a connection request to the individual KNX device
+        deviceConnectionRequest := driverModel.NewTunnelingRequest(
+            driverModel.NewTunnelingRequestDataBlock(0, 0),
+            driverModel.NewLDataReq(0, nil,
+                driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+                    driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
+                    driverModel.NewApduControlContainer(driverModel.NewApduControlConnect(), 0, false, 0),
+                    true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
+        err := m.sendRequest(
+            deviceConnectionRequest,
+            // The Gateway is now supposed to send an Ack to this request.
+            func(message interface{}) bool {
+                tunnelingRequest := driverModel.CastTunnelingRequest(message)
+                if tunnelingRequest == nil ||
+                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+                    return false
+                }
+                lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+                if lDataCon == nil {
+                    return false
+                }
+                lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+                if lDataFrameExt == nil {
+                    return false
+                }
+                // TODO: Check this too: lDataFrameExt.DestinationAddress
+                apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
+                if apduControlContainer == nil {
+                    return false
+                }
+                apduControlConnect := driverModel.CastApduControlConnect(apduControlContainer.ControlApdu)
+                return apduControlConnect != nil
+            },
+            func(message interface{}) error {
+                tunnelingRequest := driverModel.CastTunnelingRequest(message)
+                lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+                // If the error flag is set, there was an error connecting
+                if lDataCon.DataFrame.ErrorFlag {
+                    close(result)
+                }
+                lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+                apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
+                apduControlConnect := driverModel.CastApduControlConnect(apduControlContainer.ControlApdu)
+
+                // Create a new connection object and save it in the map
+                deviceConnection := &KnxDeviceConnection{
+                    counter: 0,
+                    maxApdu: 0, // TODO: Initialize this with the default max APDU Size
+                }
+                m.DeviceConnections[targetAddress] = deviceConnection
+
+                result <- *apduControlConnect
+                return nil
+            },
+            m.defaultTtl)
+        if err != nil {
+            close(result)
+        }
+    }()
+
+    return result
+}
+
+func (m *KnxNetIpConnection) sendDeviceDisconnectionRequest(targetAddress driverModel.KnxAddress) chan driverModel.ApduControlDisconnect {
+    result := make(chan driverModel.ApduControlDisconnect)
+
+    go func() {
+        // Send a connection request to the individual KNX device
+        deviceDisconnectionRequest := driverModel.NewTunnelingRequest(
+            driverModel.NewTunnelingRequestDataBlock(0, 0),
+            driverModel.NewLDataReq(0, nil,
+                driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+                    driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
+                    driverModel.NewApduControlContainer(driverModel.NewApduControlDisconnect(), 0, false, 0),
+                    true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
+        err := m.sendRequest(
+            deviceDisconnectionRequest,
+            // The Gateway is now supposed to send an Ack to this request.
+            func(message interface{}) bool {
+                tunnelingRequest := driverModel.CastTunnelingRequest(message)
+                if tunnelingRequest == nil ||
+                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+                    return false
+                }
+                lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+                if lDataCon == nil {
+                    return false
+                }
+                lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+                if lDataFrameExt == nil {
+                    return false
+                }
+                // TODO: Check this too: lDataFrameExt.DestinationAddress
+                apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
+                if apduControlContainer == nil {
+                    return false
+                }
+                apduControlDisconnect := driverModel.CastApduControlDisconnect(apduControlContainer.ControlApdu)
+                return apduControlDisconnect != nil
+            },
+            func(message interface{}) error {
+                tunnelingRequest := driverModel.CastTunnelingRequest(message)
+                lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+                // If the error flag is set, there was an error connecting
+                if lDataCon.DataFrame.ErrorFlag {
+                    close(result)
+                }
+                lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+                apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
+                apduControlDisconnect := driverModel.CastApduControlDisconnect(apduControlContainer.ControlApdu)
+
+                result <- *apduControlDisconnect
+                return nil
+            },
+            m.defaultTtl)
+        if err != nil {
+            close(result)
+        }
+    }()
+
+    return result
+}
+
+func (m *KnxNetIpConnection) sendDeviceDeviceDescriptorReadRequest(targetAddress driverModel.KnxAddress) chan driverModel.ApduDataDeviceDescriptorResponse {
+    result := make(chan driverModel.ApduDataDeviceDescriptorResponse)
+
+    connection, ok := m.DeviceConnections[targetAddress]
+    if !ok {
+        close(result)
+        return result
+    }
+
+    // Next, read the device descriptor so we know how we have to communicate with the device.
+    counter := connection.counter
+    connection.counter++
+    deviceDescriptorReadRequest := driverModel.NewTunnelingRequest(
+        driverModel.NewTunnelingRequestDataBlock(
+            // This is actually set in the KnxNetIpConnection.SendMessage method
+            0,
+            // This is actually set in the KnxNetIpConnection.SendMessage method
+            0),
+        driverModel.NewLDataReq(0, nil,
+            driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+                driverModel.NewKnxAddress(0, 0, 0),
+                KnxAddressToInt8Array(targetAddress),
+                driverModel.NewApduDataContainer(
+                    driverModel.NewApduDataDeviceDescriptorRead(0), 1, true, counter),
+                true, true, driverModel.CEMIPriority_LOW, false, false)))
+    err := m.sendRequest(
+        deviceDescriptorReadRequest,
+        func(message interface{}) bool {
+            tunnelingRequest := driverModel.CastTunnelingRequest(message)
+            if tunnelingRequest == nil ||
+                tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+                return false
+            }
+            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+            if lDataInd == nil {
+                return false
+            }
+            dataFrame := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+            if dataFrame == nil {
+                return false
+            }
+            // TODO: Check this too: dataFrame.SourceAddress
+            // TODO: Check this too: dataFrame.Apdu.Counter
+            dataContainer := driverModel.CastApduDataContainer(dataFrame.Apdu)
+            if dataContainer == nil {
+                return false
+            }
+            deviceDescriptorResponse := driverModel.CastApduDataDeviceDescriptorResponse(dataContainer.DataApdu)
+            if deviceDescriptorResponse == nil {
+                return false
+            }
+            return true
+        },
+        func(message interface{}) error {
+            tunnelingRequest := driverModel.CastTunnelingRequest(message)
+            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+            dataFrame := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+            dataContainer := driverModel.CastApduDataContainer(dataFrame.Apdu)
+            deviceDescriptorResponse := driverModel.CastApduDataDeviceDescriptorResponse(dataContainer.DataApdu)
+
+            // Send an ACK and wait for it to be delivered
+            go func() {
+                ackResults := m.sendDeviceAck(targetAddress, dataFrame.Apdu.Counter)
+                select {
+                case ackResult := <-ackResults:
+                    if !ackResult {
+                        fmt.Printf("Error getting Ack")
+                    }
+                }
+
+                // Save the device-descriptor value
+                deviceDescriptor := uint16(deviceDescriptorResponse.Data[0])<<8 | uint16(deviceDescriptorResponse.Data[1])
+                connection.deviceDescriptor = deviceDescriptor
+
+                result <- *deviceDescriptorResponse
+            }()
+
+            return nil
+        },
+        time.Second*5)
+    if err != nil {
+        close(result)
+    }
+    return result
+}
+
+func (m *KnxNetIpConnection) sendDevicePropertyReadRequest(targetAddress driverModel.KnxAddress, objectId uint8, propertyId uint8) chan KnxReadResult {
+    result := make(chan KnxReadResult)
+
+    connection, ok := m.DeviceConnections[targetAddress]
+    if !ok {
+        close(result)
+        result <- KnxReadResult{
+            returnCode: apiModel.PlcResponseCode_INTERNAL_ERROR,
+        }
+    }
+
+    // Next, read the device descriptor so we know how we have to communicate with the device.
+    counter := connection.counter
+    connection.counter++
+
+    // Send the property read request and wait for a confirmation that this property is readable.
+    propertyReadRequest := driverModel.NewTunnelingRequest(
+        driverModel.NewTunnelingRequestDataBlock(0, 0),
+        driverModel.NewLDataReq(0, nil,
+            driverModel.NewLDataFrameDataExt(false, 6, 0,
+                driverModel.NewKnxAddress(0, 0, 0),
+                KnxAddressToInt8Array(targetAddress),
+                driverModel.NewApduDataContainer(
+                    driverModel.NewApduDataOther(
+                        // TODO: The counter should be incremented per KNX individual address
+                        driverModel.NewApduDataExtPropertyValueRead(objectId, propertyId, 1, 1)),
+                    5, true, counter),
+                true, true, driverModel.CEMIPriority_LOW, false, false)))
+    err := m.sendRequest(
+        propertyReadRequest,
+        func(message interface{}) bool {
+            tunnelingRequest := driverModel.CastTunnelingRequest(message)
+            if tunnelingRequest == nil ||
+                tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+                return false
+            }
+            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+            if lDataInd == nil {
+                return false
+            }
+            var apdu *driverModel.Apdu
+            dataFrameExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+            if dataFrameExt != nil {
+                apdu = dataFrameExt.Apdu
+            } else {
+                dataFrame := driverModel.CastLDataFrameData(lDataInd.DataFrame)
+                if dataFrame != nil {
+                    apdu = dataFrame.Apdu
+                }
+            }
+            if apdu == nil {
+                return false
+            }
+            // TODO: Check this too: dataFrame.SourceAddress
+            // TODO: Check this too: dataFrame.Apdu.Counter
+            dataContainer := driverModel.CastApduDataContainer(apdu)
+            if dataContainer == nil {
+                return false
+            }
+            dataApduOther := driverModel.CastApduDataOther(dataContainer.DataApdu)
+            if dataApduOther == nil {
+                return false
+            }
+            propertyValueResponse := driverModel.CastApduDataExtPropertyValueResponse(dataApduOther.ExtendedApdu)
+            if propertyValueResponse == nil {
+                return false
+            }
+            return propertyValueResponse.ObjectIndex == objectId && propertyValueResponse.PropertyId == propertyId
+        },
+        func(message interface{}) error {
+            tunnelingRequest := driverModel.CastTunnelingRequest(message)
+            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+            var apdu *driverModel.Apdu
+            dataFrameExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+            if dataFrameExt != nil {
+                apdu = dataFrameExt.Apdu
+            } else {
+                dataFrame := driverModel.CastLDataFrameData(lDataInd.DataFrame)
+                if dataFrame != nil {
+                    apdu = dataFrame.Apdu
+                }
+            }
+            dataContainer := driverModel.CastApduDataContainer(apdu)
+            dataApduOther := driverModel.CastApduDataOther(dataContainer.DataApdu)
+            propertyValueResponse := driverModel.CastApduDataExtPropertyValueResponse(dataApduOther.ExtendedApdu)
+
+            // Send an ACK and wait for it to be delivered
+            go func() {
+                ackResults := m.sendDeviceAck(targetAddress, apdu.Counter)
+                select {
+                case ackResult := <-ackResults:
+                    if !ackResult {
+                        fmt.Printf("Error getting Ack")
+                    }
+                }
+
+                // If the count is 0, then this property doesn't exist or the user has no permission to read it.
+                if propertyValueResponse.Count == 0 {
+                    result <- KnxReadResult{
+                        returnCode: apiModel.PlcResponseCode_NOT_FOUND,
+                    }
+                } else {
+                    // Find out the type of the property
+                    var objectType *driverModel.KnxInterfaceObjectType
+                    for curObjectType := driverModel.KnxInterfaceObjectType_OT_UNKNOWN; curObjectType <= driverModel.KnxInterfaceObjectType_OT_SUNBLIND_SENSOR_BASIC; curObjectType++ {
+                        if curObjectType.Code() == strconv.Itoa(int(objectId)) {
+                            objectType = &curObjectType
+                            break
+                        }
+                    }
+                    property := driverModel.KnxInterfaceObjectProperty_PID_UNKNOWN
+                    if objectType != nil {
+                        for curProperty := driverModel.KnxInterfaceObjectProperty_PID_UNKNOWN; curProperty <= driverModel.KnxInterfaceObjectProperty_PID_SUNBLIND_SENSOR_BASIC_ENABLE_TOGGLE_MODE; curProperty++ {
+                            if curProperty.PropertyId() == propertyId {
+                                if curProperty.ObjectType() == driverModel.KnxInterfaceObjectType_OT_GENERAL || curProperty.ObjectType() == *objectType {
+                                    property = curProperty
+                                    break
+                                }
+                            }
+                        }
+                    }
+
+                    // Parse the data according to the property type information
+                    propertyDataType := property.PropertyDataType()
+                    dataLength := uint8(len(propertyValueResponse.Data))
+                    data := propertyValueResponse.Data
+                    rb := utils.NewReadBuffer(data)
+                    plcValue, err := driverModel.KnxPropertyParse(rb, propertyDataType, dataLength)
+
+                    // Return the result
+                    if err != nil {
+                        result <- KnxReadResult{
+                            returnCode: apiModel.PlcResponseCode_INTERNAL_ERROR,
+                            value:      nil,
+                        }
+                    } else {
+                        result <- KnxReadResult{
+                            returnCode: apiModel.PlcResponseCode_OK,
+                            value:      &plcValue,
+                        }
+                    }
+                }
+
+            }()
+            return nil
+        },
+        time.Second*5)
+
+    if err != nil {
+        close(result)
+    }
+    return result
+}
+
+func (m *KnxNetIpConnection) sendDeviceAck(targetAddress driverModel.KnxAddress, counter uint8) chan bool {
+    result := make(chan bool)
+
+    ack := driverModel.NewTunnelingRequest(
+        driverModel.NewTunnelingRequestDataBlock(0, 0),
+        driverModel.NewLDataReq(0, nil,
+            driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+                driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
+                driverModel.NewApduControlContainer(driverModel.NewApduControlAck(), 0, true, counter),
+                true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
+    err := m.sendRequest(
+        ack,
+        func(message interface{}) bool {
+            tunnelingRequest := driverModel.CastTunnelingRequest(message)
+            if tunnelingRequest == nil ||
+                tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+                return false
+            }
+            lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+            if lDataCon == nil {
+                return false
+            }
+            dataFrame := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+            if dataFrame == nil {
+                return false
+            }
+            // TODO: Check this too: dataFrame.SourceAddress (This should match the targetAddress)
+            // TODO: Check this too: dataFrame.DestinationAddress (This should match our KNX Address)
+            // TODO: Check this too: dataFrame.Apdu.Counter
+            controlContainer := driverModel.CastApduControlContainer(dataFrame.Apdu)
+            if controlContainer == nil {
+                return false
+            }
+            dataApduAck := driverModel.CastApduControlAck(controlContainer.ControlApdu)
+            if dataApduAck == nil {
+                return false
+            }
+            return true
+        },
+        func(message interface{}) error {
+            result <- true
+            return nil
+        },
+        time.Second*5)
+
+    if err != nil {
+        result <- false
+    }
+
+    return result
+}
+
+func (m *KnxNetIpConnection) sendRequest(request *driverModel.KnxNetIpMessage, acceptsMessage spi.AcceptsMessage, handleMessage spi.HandleMessage, ttl time.Duration) error {
     // If this is a tunneling request, we need to update the communicationChannelId and assign a sequenceCounter
     tunnelingRequest := driverModel.CastTunnelingRequest(request)
     if tunnelingRequest != nil {
@@ -517,6 +1085,21 @@ func (m *KnxNetIpConnection) SendRequest(request *driverModel.KnxNetIpMessage, a
     return m.messageCodec.SendRequest(request, acceptsMessage, handleMessage, ttl)
 }
 
+func (m *KnxNetIpConnection) getLocalAddress() (*net.UDPAddr, error) {
+    transportInstanceExposer, ok := m.messageCodec.(spi.TransportInstanceExposer)
+    if !ok {
+        return nil, errors.New("used transport, is not a TransportInstanceExposer")
+    }
+
+    // Prepare a SearchReq
+    udpTransportInstance, ok := transportInstanceExposer.GetTransportInstance().(*udp.UdpTransportInstance)
+    if !ok {
+        return nil, errors.New("used transport, is not a UdpTransportInstance")
+    }
+
+    return udpTransportInstance.LocalAddress, nil
+}
+
 func (m *KnxNetIpConnection) interceptIncomingMessage(interface{}) {
     if m.connectionStateTimer != nil {
         // Reset the timer for sending the ConnectionStateRequest
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpDiscoverer.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpDiscoverer.go
index ff03989..213ab9e 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpDiscoverer.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpDiscoverer.go
@@ -110,13 +110,12 @@ func (d *KnxNetIpDiscoverer) Discover(callback func(event model.PlcDiscoveryEven
 				return errors.New("couldn't cast transport instance to UDP transport instance")
 			}
 			localAddress := udpTransportInstance.LocalAddress
+			localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP))
 
 			// Prepare the discovery packet data
-			searchRequestMessage := driverModel.NewSearchRequest(driverModel.NewHPAIDiscoveryEndpoint(
-				driverModel.HostProtocolCode_IPV4_UDP,
-				driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP.To4())),
-				uint16(localAddress.Port)))
-
+            discoveryEndpoint := driverModel.NewHPAIDiscoveryEndpoint(
+                driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port))
+            searchRequestMessage := driverModel.NewSearchRequest(discoveryEndpoint)
 			err = codec.SendRequest(
 				searchRequestMessage,
 				func(message interface{}) bool {
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpMessageCodec.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpMessageCodec.go
index 479d4b6..0438baf 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpMessageCodec.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpMessageCodec.go
@@ -150,13 +150,12 @@ func work(m *KnxNetIpMessageCodec) {
 			fmt.Printf("got an error reading from transport %s", err.Error())
 		} else if message != nil {
 			// If this message is a simple KNXNet/IP UDP Ack, ignore it for now
-			// TODO: In the future use these to see if a packet needs to be received
 			tunnelingResponse := model.CastTunnelingResponse(message)
 			if tunnelingResponse != nil {
 				continue
 			}
 
-            // If this is an incoming KNXNet/IP UDP Packet, automatically send an ACK
+            // If this is an incoming tunneling request, automatically send a tunneling ACK
 			tunnelingRequest := model.CastTunnelingRequest(message)
 			if tunnelingRequest != nil {
 				response := model.NewTunnelingResponse(
@@ -168,8 +167,8 @@ func work(m *KnxNetIpMessageCodec) {
 				_ = m.Send(response)
 			}
 
-			// Otherwise handle it
-			now := time.Now()
+			// Handle the packet itself
+			//now := time.Now()
 			// Give a message interceptor a chance to intercept
 			if m.messageInterceptor != nil {
 				m.messageInterceptor(message)
@@ -178,11 +177,11 @@ func work(m *KnxNetIpMessageCodec) {
 			messageHandled := false
 			for index, expectation := range m.expectations {
 				// Check if this expectation has expired.
-				if now.After(expectation.expiration) {
+				/*if now.After(expectation.expiration) {
 					// Remove this expectation from the list.
 					m.expectations = append(m.expectations[:index], m.expectations[index+1:]...)
-					break
-				}
+					continue
+				}*/
 
 				// Check if the current message matches the expectations
 				// If it does, let it handle the message.
@@ -192,8 +191,9 @@ func work(m *KnxNetIpMessageCodec) {
 						messageHandled = true
 						// Remove this expectation from the list.
 						m.expectations = append(m.expectations[:index], m.expectations[index+1:]...)
-					}
-					break
+					} else {
+					    fmt.Printf("Got error")
+                    }
 				}
 			}
 
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go
index 7eb29bc..f16eb2d 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go
@@ -19,8 +19,6 @@
 package knxnetip
 
 import (
-    "errors"
-    "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite"
     driverModel "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
     "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
     internalModel "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
@@ -29,7 +27,6 @@ import (
     apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
     apiValues "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
     "strconv"
-    "time"
 )
 
 type KnxNetIpReader struct {
@@ -80,31 +77,55 @@ func (m KnxNetIpReader) Read(readRequest apiModel.PlcReadRequest) <-chan apiMode
 
         // Process the direct properties.
         // Connect to each knx device and read all of the properties on that particular device.
-        // Finish up by explicitly disconnecting after all properties on the device have been read.
-        for deviceAddress, fields := range directProperties {
-            // Connect to the device
-            err := m.connectToDevice(deviceAddress)
-            // If something went wrong all field for this device are equally failed
-            if err != nil {
-                for fieldName := range fields {
-                    responseCodes[fieldName] = apiModel.PlcResponseCode_INVALID_ADDRESS
+        for targetAddress, fields := range directProperties {
+            // Collect all the properties on this device
+            for fieldName, field := range fields {
+                // TODO: We'll add this as time progresses, for now we only support fully qualified addresses
+                if field.IsPatternField() {
+                    responseCodes[fieldName] = apiModel.PlcResponseCode_UNSUPPORTED
                     plcValues[fieldName] = nil
+                    continue
                 }
-                continue
-            }
 
-            // Collect all the properties on this device
-            counter := uint8(1)
-            for fieldName, field := range fields {
-                responseCode, plcValue := m.readDeviceProperty(field, counter)
-                responseCodes[fieldName] = responseCode
-                plcValues[fieldName] = plcValue
-                counter++
+                objectId, _ := strconv.Atoi(field.ObjectId)
+                propertyId, _ := strconv.Atoi(field.PropertyId)
+
+                results := m.connection.ReadDeviceProperty(targetAddress, uint8(objectId), uint8(propertyId))
+                select {
+                case result := <-results:
+                    // Get the DataType for the given property
+                    var propertyDataType *driverModel.KnxPropertyDataType
+                    for i := driverModel.KnxInterfaceObjectType_OT_UNKNOWN; i <= driverModel.KnxInterfaceObjectType_OT_SUNBLIND_SENSOR_BASIC; i++ {
+                        if i.Code() == strconv.Itoa(objectId) {
+                            for j := driverModel.KnxInterfaceObjectProperty_PID_UNKNOWN; j <= driverModel.KnxInterfaceObjectProperty_PID_SUNBLIND_SENSOR_BASIC_ENABLE_TOGGLE_MODE; j++ {
+                                if j.PropertyId() == uint8(propertyId) {
+                                    pdt := j.PropertyDataType()
+                                    propertyDataType = &pdt
+                                    break
+                                }
+                            }
+                            if propertyDataType != nil {
+                                break
+                            }
+                        }
+                    }
+                    if propertyDataType != nil {
+                        responseCodes[fieldName] = result.returnCode
+                        if result.returnCode == apiModel.PlcResponseCode_OK {
+                            plcValues[fieldName] = *result.value
+                        } else {
+                            plcValues[fieldName] = nil
+                        }
+                    } else {
+                        // TODO: If the datatype is unknown, return the raw data as PlcList of bytes.
+                        responseCodes[fieldName] = apiModel.PlcResponseCode_INTERNAL_ERROR
+                        plcValues[fieldName] = nil
+                    }
+                /*default:
+                    responseCodes[fieldName] = apiModel.PlcResponseCode_INVALID_ADDRESS
+                    plcValues[fieldName] = nil*/
+                }
             }
-
-            // Disconnect from the device
-            _ = m.disconnectFromDevice(*m.connection.ClientKnxAddress, deviceAddress)
-            // In this case we ignore if something goes wrong
         }
 
         // Get the group address values from the cache
@@ -125,304 +146,6 @@ func (m KnxNetIpReader) Read(readRequest apiModel.PlcReadRequest) <-chan apiMode
     return resultChan
 }
 
-func (m KnxNetIpReader) connectToDevice(targetAddress driverModel.KnxAddress) error {
-    connectionSuccess := make(chan bool)
-    deviceConnectionRequest := driverModel.NewTunnelingRequest(
-        driverModel.NewTunnelingRequestDataBlock(0, 0),
-        driverModel.NewLDataReq(0, nil,
-            driverModel.NewLDataFrameDataExt(false, 6, 0,
-                driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
-                driverModel.NewApduControlContainer(driverModel.NewApduControlConnect(), 0, false, 0),
-                true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
-
-    // Send the request
-    err := m.connection.SendRequest(
-        deviceConnectionRequest,
-        // The Gateway is now supposed to send an Ack to this request.
-        func(message interface{}) bool {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            if tunnelingRequest == nil ||
-                tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.connection.CommunicationChannelId {
-                return false
-            }
-            lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-            return lDataCon != nil
-        },
-        func(message interface{}) error {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-            // If the error flag is set, there was an error connecting
-            if lDataCon.DataFrame.ErrorFlag {
-                connectionSuccess <- false
-            }
-
-            // Now for some reason it seems as if we need to implement a Device Descriptor read.
-            deviceDescriptorReadRequest := driverModel.NewTunnelingRequest(
-                driverModel.NewTunnelingRequestDataBlock(0, 0),
-                driverModel.NewLDataReq(0, nil,
-                    driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
-                        driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
-                        driverModel.NewApduDataContainer(driverModel.NewApduDataDeviceDescriptorRead(0), 1, true, 0),
-                        true, true, driverModel.CEMIPriority_LOW, false, false)))
-            _ = m.connection.SendRequest(
-                deviceDescriptorReadRequest,
-                func(message interface{}) bool {
-                    tunnelingRequest := driverModel.CastTunnelingRequest(message)
-                    if tunnelingRequest == nil ||
-                        tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.connection.CommunicationChannelId {
-                        return false
-                    }
-                    lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
-                    if lDataInd == nil {
-                        return false
-                    }
-                    dataFrame := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
-                    if dataFrame == nil {
-                        return false
-                    }
-                    dataContainer := driverModel.CastApduDataContainer(dataFrame.Apdu)
-                    if dataContainer == nil {
-                        return false
-                    }
-                    deviceDescriptorResponse := driverModel.CastApduDataDeviceDescriptorResponse(dataContainer.DataApdu)
-                    if deviceDescriptorResponse == nil {
-                        return false
-                    }
-                    return true
-                    //return *dataFrame.Apdu.Apci == driverModel.APCI_DEVICE_DESCRIPTOR_RESPONSE_PDU
-                    // TODO: Do something with the request ...
-                },
-                func(message interface{}) error {
-                    // Send back an ACK
-                    _ = m.connection.Send(
-                        driverModel.NewTunnelingRequest(
-                            driverModel.NewTunnelingRequestDataBlock(0, 0),
-                            driverModel.NewLDataReq(0, nil,
-                                driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
-                                    driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
-                                    driverModel.NewApduControlContainer(driverModel.NewApduControlAck(), 0, true, 0),
-                                    true, true, driverModel.CEMIPriority_SYSTEM, false, false))))
-                    // Now we can finally read properties.
-                    connectionSuccess <- true
-                    return nil
-                },
-                time.Second*5)
-            return nil
-        },
-        time.Second*1)
-
-    if err != nil {
-        return errors.New("could not connect to device (Error sending connection request)")
-    }
-    select {
-    case result := <-connectionSuccess:
-        if !result {
-            return errors.New("could not connect to device (NACK)")
-        }
-    case <-time.After(time.Second * 5):
-        return errors.New("could not connect to device (Timeout)")
-    }
-    return nil
-}
-
-func (m KnxNetIpReader) disconnectFromDevice(sourceAddress driverModel.KnxAddress, targetAddress driverModel.KnxAddress) error {
-    deviceConnectionRequest := driverModel.NewTunnelingRequest(
-        driverModel.NewTunnelingRequestDataBlock(0, 0),
-        driverModel.NewLDataReq(0, nil,
-            driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
-                &sourceAddress, KnxAddressToInt8Array(targetAddress),
-                driverModel.NewApduControlContainer(driverModel.NewApduControlDisconnect(), 0, false, 0),
-                true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
-
-    // Send the request
-    connectionSuccess := make(chan bool)
-    err := m.connection.SendRequest(
-        deviceConnectionRequest,
-        // The Gateway is now supposed to send an Ack to this request.
-        func(message interface{}) bool {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            if tunnelingRequest == nil ||
-                tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.connection.CommunicationChannelId {
-                return false
-            }
-            lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-            if lDataCon == nil {
-                return false
-            }
-            frameDataExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
-            if frameDataExt == nil {
-                return false
-            }
-            return true
-            //return frameDataExt.Control == true && frameDataExt.ControlType == nil
-        },
-        func(message interface{}) error {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-            _ = driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
-            //if *frameDataExt.ControlType == driverModel.ControlType_DISCONNECT {
-            connectionSuccess <- false
-            //}
-            return nil
-        },
-        time.Second*1)
-
-    if err != nil {
-        return errors.New("could not connect to device (Error sending connection request)")
-    }
-    select {
-    case result := <-connectionSuccess:
-        if !result {
-            return errors.New("could not connect to device (NACK)")
-        }
-    case <-time.After(time.Second * 5):
-        return errors.New("could not connect to device (Timeout)")
-    }
-    return nil
-}
-
-type readPropertyResult struct {
-    plcValue     apiValues.PlcValue
-    responseCode apiModel.PlcResponseCode
-}
-
-func (m KnxNetIpReader) readDeviceProperty(field KnxNetIpDevicePropertyAddressPlcField, counter uint8) (apiModel.PlcResponseCode, apiValues.PlcValue) {
-    // TODO: We'll add this as time progresses, for now we only support fully qualified addresses
-    if field.IsPatternField() {
-        return apiModel.PlcResponseCode_UNSUPPORTED, nil
-    }
-
-    destinationAddress := FieldToKnxAddress(field)
-    destinationAddressBuffer := utils.NewWriteBuffer()
-    err := destinationAddress.Serialize(*destinationAddressBuffer)
-    if err != nil {
-        return apiModel.PlcResponseCode_INTERNAL_ERROR, nil
-    }
-    destinationAddressData := utils.Uint8ArrayToInt8Array(destinationAddressBuffer.GetBytes())
-    objectId, _ := strconv.Atoi(field.ObjectId)
-    propertyId, _ := strconv.Atoi(field.PropertyId)
-
-    request := driverModel.NewTunnelingRequest(
-        driverModel.NewTunnelingRequestDataBlock(0, 0),
-        driverModel.NewLDataReq(0, nil,
-            driverModel.NewLDataFrameDataExt(false, 6, 0,
-                driverModel.NewKnxAddress(0, 0, 0),
-                destinationAddressData,
-                driverModel.NewApduDataContainer(
-                    driverModel.NewApduDataOther(
-                        // TODO: The counter should be incremented per KNX individual address
-                        driverModel.NewApduDataExtPropertyValueRead(uint8(objectId), uint8(propertyId), 1, 1)), 5, true, counter),
-                true, true, driverModel.CEMIPriority_LOW, false, false)))
-
-    result := make(chan readPropertyResult)
-    err = m.connection.SendRequest(
-        request,
-        // Even if there are multiple messages being exchanged because of the request
-        // We are not interested in most of them. The one containing the response is
-        // an LData.ind from the destination address to our client address with the given
-        // object-id and property-id.
-        func(message interface{}) bool {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            if tunnelingRequest == nil {
-                return false
-            }
-            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
-            if lDataInd == nil {
-                return false
-            }
-            dataFrameExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
-            if dataFrameExt != nil && dataFrameExt.Apdu != nil {
-                apduDataContainer := driverModel.CastApduDataContainer(dataFrameExt.Apdu)
-                if apduDataContainer == nil {
-                    return false
-                }
-                otherPdu := driverModel.CastApduDataOther(apduDataContainer.DataApdu)
-                if otherPdu == nil {
-                    return false
-                }
-                propertyValueResponse := driverModel.CastApduDataExtPropertyValueResponse(otherPdu.ExtendedApdu)
-                if propertyValueResponse == nil {
-                    return false
-                }
-                if *dataFrameExt.SourceAddress != *destinationAddress {
-                    return false
-                }
-                if *Int8ArrayToKnxAddress(dataFrameExt.DestinationAddress) != *m.connection.ClientKnxAddress {
-                    return false
-                }
-                curObjectId := propertyValueResponse.ObjectIndex
-                curPropertyId := propertyValueResponse.PropertyId
-                if curObjectId == uint8(objectId) && curPropertyId == uint8(propertyId) {
-                    return true
-                }
-            }
-            return false
-        },
-        func(message interface{}) error {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
-            dataFrameExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
-            apduDataContainer := driverModel.CastApduDataContainer(dataFrameExt.Apdu)
-            otherPdu := driverModel.CastApduDataOther(apduDataContainer.DataApdu)
-            propertyValueResponse := driverModel.CastApduDataExtPropertyValueResponse(otherPdu.ExtendedApdu)
-
-            readBuffer := utils.NewReadBuffer(propertyValueResponse.Data)
-            propertyCount := propertyValueResponse.Count
-
-            // If the return is a count of 0, then we can't access this property (Doesn't exist or not allowed to)
-            // As we don't have a return code for "doesn't exist or doesn't have access to" we'll stick to "not found"
-            // as this can be understood as "found no property we have access to"
-            // ("03_03_07 Application Layer v01.06.02 AS" Page 52)
-            var propResult readPropertyResult
-            if propertyCount == 0 {
-                propResult = readPropertyResult{
-                    responseCode: apiModel.PlcResponseCode_NOT_FOUND,
-                }
-            } else {
-                // Depending on the object id and property id, parse the remaining data accordingly.
-                property := driverModel.KnxInterfaceObjectProperty_PID_UNKNOWN
-                for i := driverModel.KnxInterfaceObjectProperty_PID_UNKNOWN; i < driverModel.KnxInterfaceObjectProperty_PID_SUNBLIND_SENSOR_BASIC_ENABLE_TOGGLE_MODE; i++ {
-                    // If the propertyId matches and this is either a general object or the object id matches, add it to the result
-                    if i.PropertyId() == uint8(propertyId) && (i.ObjectType().Code() == "G" || i.ObjectType().Code() == strconv.Itoa(objectId)) {
-                        property = i
-                        break
-                    }
-                }
-
-                // Parse the payload according to the specified datatype
-                dataType := property.PropertyDataType()
-                plcValue := readwrite.ParsePropertyDataType(*readBuffer, dataType, dataType.SizeInBytes())
-                propResult = readPropertyResult{
-                    plcValue:     plcValue,
-                    responseCode: apiModel.PlcResponseCode_OK,
-                }
-            }
-
-            // Send back an ACK
-            _ = m.connection.Send(
-                driverModel.NewTunnelingRequest(
-                    driverModel.NewTunnelingRequestDataBlock(0, 0),
-                    driverModel.NewLDataReq(0, nil,
-                        driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
-                            driverModel.NewKnxAddress(0, 0, 0), destinationAddressData,
-                            driverModel.NewApduControlContainer(driverModel.NewApduControlAck(), 0, true, dataFrameExt.Apdu.Counter),
-                            true, true, driverModel.CEMIPriority_SYSTEM, false, false))))
-
-            result <- propResult
-            return nil
-        },
-        time.Second*5)
-
-    select {
-    case value := <-result:
-        responseCode := value.responseCode
-        plcValue := value.plcValue
-        return responseCode, plcValue
-        /*case <-time.After(time.Second * 5):
-          return apiModel.PlcResponseCode_REMOTE_ERROR, nil*/
-    }
-}
-
 func (m KnxNetIpReader) readGroupAddress(field KnxNetIpField) (apiModel.PlcResponseCode, apiValues.PlcValue) {
     // Pattern fields can match more than one value, therefore we have to handle things differently
     if field.IsPatternField() {
diff --git a/plc4go/internal/plc4go/knxnetip/readwrite/model/KnxProperty.go b/plc4go/internal/plc4go/knxnetip/readwrite/model/KnxProperty.go
new file mode 100644
index 0000000..177a78f
--- /dev/null
+++ b/plc4go/internal/plc4go/knxnetip/readwrite/model/KnxProperty.go
@@ -0,0 +1,1407 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+package model
+
+import (
+            "errors"
+            "github.com/apache/plc4x/plc4go/internal/plc4go/spi/values"
+            "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
+            api "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+)
+
+func KnxPropertyParse(io *utils.ReadBuffer, propertyType KnxPropertyDataType, dataLength uint8) (api.PlcValue, error) {
+    switch {
+        case propertyType == KnxPropertyDataType_PDT_CONTROL: // BOOL
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(7); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadBit()
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcBOOL(value), nil
+        case propertyType == KnxPropertyDataType_PDT_CHAR: // SINT
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadInt8(8)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcSINT(value), nil
+        case propertyType == KnxPropertyDataType_PDT_UNSIGNED_CHAR: // USINT
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadUint8(8)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcUSINT(value), nil
+        case propertyType == KnxPropertyDataType_PDT_INT: // INT
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadInt16(16)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcINT(value), nil
+        case propertyType == KnxPropertyDataType_PDT_UNSIGNED_INT: // UINT
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadUint16(16)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcUINT(value), nil
+        case propertyType == KnxPropertyDataType_PDT_KNX_FLOAT: // REAL
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadFloat32(true, 4, 11)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcREAL(value), nil
+        case propertyType == KnxPropertyDataType_PDT_DATE: // Struct
+            _map := map[string]api.PlcValue{}
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(3); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (dayOfMonth)
+            dayOfMonth, _dayOfMonthErr := io.ReadUint8(5)
+            if _dayOfMonthErr != nil {
+                return nil, errors.New("Error parsing 'dayOfMonth' field " + _dayOfMonthErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(dayOfMonth)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(4); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (month)
+            month, _monthErr := io.ReadUint8(4)
+            if _monthErr != nil {
+                return nil, errors.New("Error parsing 'month' field " + _monthErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(month)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(1); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (year)
+            year, _yearErr := io.ReadUint8(7)
+            if _yearErr != nil {
+                return nil, errors.New("Error parsing 'year' field " + _yearErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(year)
+            return values.NewPlcStruct(_map), nil
+        case propertyType == KnxPropertyDataType_PDT_TIME: // Struct
+            _map := map[string]api.PlcValue{}
+
+            // Simple Field (day)
+            day, _dayErr := io.ReadUint8(3)
+            if _dayErr != nil {
+                return nil, errors.New("Error parsing 'day' field " + _dayErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(day)
+
+            // Simple Field (hour)
+            hour, _hourErr := io.ReadUint8(5)
+            if _hourErr != nil {
+                return nil, errors.New("Error parsing 'hour' field " + _hourErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(hour)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(2); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (minutes)
+            minutes, _minutesErr := io.ReadUint8(6)
+            if _minutesErr != nil {
+                return nil, errors.New("Error parsing 'minutes' field " + _minutesErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(minutes)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(2); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (seconds)
+            seconds, _secondsErr := io.ReadUint8(6)
+            if _secondsErr != nil {
+                return nil, errors.New("Error parsing 'seconds' field " + _secondsErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(seconds)
+            return values.NewPlcStruct(_map), nil
+        case propertyType == KnxPropertyDataType_PDT_LONG: // DINT
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadInt32(32)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcDINT(value), nil
+        case propertyType == KnxPropertyDataType_PDT_UNSIGNED_LONG: // UDINT
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadUint32(32)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcUDINT(value), nil
+        case propertyType == KnxPropertyDataType_PDT_FLOAT: // REAL
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadFloat32(true, 8, 23)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcREAL(value), nil
+        case propertyType == KnxPropertyDataType_PDT_DOUBLE: // LREAL
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadFloat64(true, 11, 52)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcLREAL(value), nil
+        case propertyType == KnxPropertyDataType_PDT_CHAR_BLOCK: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((10)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_POLL_GROUP_SETTINGS: // Struct
+            _map := map[string]api.PlcValue{}
+
+            // Array Field (groupAddress)
+            var groupAddress []api.PlcValue
+            for i := 0; i < int((2)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                groupAddress = append(groupAddress, values.NewPlcUSINT(_item))
+            }
+
+            // Simple Field (disable)
+            disable, _disableErr := io.ReadBit()
+            if _disableErr != nil {
+                return nil, errors.New("Error parsing 'disable' field " + _disableErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(disable)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(3); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (pollingSoftNr)
+            pollingSoftNr, _pollingSoftNrErr := io.ReadUint8(4)
+            if _pollingSoftNrErr != nil {
+                return nil, errors.New("Error parsing 'pollingSoftNr' field " + _pollingSoftNrErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(pollingSoftNr)
+            return values.NewPlcStruct(_map), nil
+        case propertyType == KnxPropertyDataType_PDT_SHORT_CHAR_BLOCK: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((5)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_DATE_TIME: // Struct
+            _map := map[string]api.PlcValue{}
+
+            // Simple Field (year)
+            year, _yearErr := io.ReadUint8(8)
+            if _yearErr != nil {
+                return nil, errors.New("Error parsing 'year' field " + _yearErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(year)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(4); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (month)
+            month, _monthErr := io.ReadUint8(4)
+            if _monthErr != nil {
+                return nil, errors.New("Error parsing 'month' field " + _monthErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(month)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(3); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (dayofmonth)
+            dayofmonth, _dayofmonthErr := io.ReadUint8(5)
+            if _dayofmonthErr != nil {
+                return nil, errors.New("Error parsing 'dayofmonth' field " + _dayofmonthErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(dayofmonth)
+
+            // Simple Field (dayofweek)
+            dayofweek, _dayofweekErr := io.ReadUint8(3)
+            if _dayofweekErr != nil {
+                return nil, errors.New("Error parsing 'dayofweek' field " + _dayofweekErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(dayofweek)
+
+            // Simple Field (hourofday)
+            hourofday, _hourofdayErr := io.ReadUint8(5)
+            if _hourofdayErr != nil {
+                return nil, errors.New("Error parsing 'hourofday' field " + _hourofdayErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(hourofday)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(2); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (minutes)
+            minutes, _minutesErr := io.ReadUint8(6)
+            if _minutesErr != nil {
+                return nil, errors.New("Error parsing 'minutes' field " + _minutesErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(minutes)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(2); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (seconds)
+            seconds, _secondsErr := io.ReadUint8(6)
+            if _secondsErr != nil {
+                return nil, errors.New("Error parsing 'seconds' field " + _secondsErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(seconds)
+
+            // Simple Field (fault)
+            fault, _faultErr := io.ReadBit()
+            if _faultErr != nil {
+                return nil, errors.New("Error parsing 'fault' field " + _faultErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(fault)
+
+            // Simple Field (workingDay)
+            workingDay, _workingDayErr := io.ReadBit()
+            if _workingDayErr != nil {
+                return nil, errors.New("Error parsing 'workingDay' field " + _workingDayErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(workingDay)
+
+            // Simple Field (noWd)
+            noWd, _noWdErr := io.ReadBit()
+            if _noWdErr != nil {
+                return nil, errors.New("Error parsing 'noWd' field " + _noWdErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(noWd)
+
+            // Simple Field (noYear)
+            noYear, _noYearErr := io.ReadBit()
+            if _noYearErr != nil {
+                return nil, errors.New("Error parsing 'noYear' field " + _noYearErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(noYear)
+
+            // Simple Field (noDate)
+            noDate, _noDateErr := io.ReadBit()
+            if _noDateErr != nil {
+                return nil, errors.New("Error parsing 'noDate' field " + _noDateErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(noDate)
+
+            // Simple Field (noDayOfWeek)
+            noDayOfWeek, _noDayOfWeekErr := io.ReadBit()
+            if _noDayOfWeekErr != nil {
+                return nil, errors.New("Error parsing 'noDayOfWeek' field " + _noDayOfWeekErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(noDayOfWeek)
+
+            // Simple Field (noTime)
+            noTime, _noTimeErr := io.ReadBit()
+            if _noTimeErr != nil {
+                return nil, errors.New("Error parsing 'noTime' field " + _noTimeErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(noTime)
+
+            // Simple Field (standardSummerTime)
+            standardSummerTime, _standardSummerTimeErr := io.ReadBit()
+            if _standardSummerTimeErr != nil {
+                return nil, errors.New("Error parsing 'standardSummerTime' field " + _standardSummerTimeErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(standardSummerTime)
+
+            // Simple Field (qualityOfClock)
+            qualityOfClock, _qualityOfClockErr := io.ReadBit()
+            if _qualityOfClockErr != nil {
+                return nil, errors.New("Error parsing 'qualityOfClock' field " + _qualityOfClockErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(qualityOfClock)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(7); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+            return values.NewPlcStruct(_map), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_01: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((1)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_02: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((2)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_03: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((3)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_04: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((4)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_05: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((5)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_06: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((6)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_07: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((7)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_08: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((8)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_09: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((9)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_10: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((10)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_11: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((11)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_12: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((12)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_13: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((13)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_14: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((14)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_15: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((15)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_16: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((16)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_17: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((17)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_18: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((18)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_19: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((19)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_20: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((20)); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_VERSION: // Struct
+            _map := map[string]api.PlcValue{}
+
+            // Simple Field (magicNumber)
+            magicNumber, _magicNumberErr := io.ReadUint8(5)
+            if _magicNumberErr != nil {
+                return nil, errors.New("Error parsing 'magicNumber' field " + _magicNumberErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(magicNumber)
+
+            // Simple Field (versionNumber)
+            versionNumber, _versionNumberErr := io.ReadUint8(5)
+            if _versionNumberErr != nil {
+                return nil, errors.New("Error parsing 'versionNumber' field " + _versionNumberErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(versionNumber)
+
+            // Simple Field (revisionNumber)
+            revisionNumber, _revisionNumberErr := io.ReadUint8(6)
+            if _revisionNumberErr != nil {
+                return nil, errors.New("Error parsing 'revisionNumber' field " + _revisionNumberErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(revisionNumber)
+            return values.NewPlcStruct(_map), nil
+        case propertyType == KnxPropertyDataType_PDT_ALARM_INFO: // Struct
+            _map := map[string]api.PlcValue{}
+
+            // Simple Field (logNumber)
+            logNumber, _logNumberErr := io.ReadUint8(8)
+            if _logNumberErr != nil {
+                return nil, errors.New("Error parsing 'logNumber' field " + _logNumberErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(logNumber)
+
+            // Simple Field (alarmPriority)
+            alarmPriority, _alarmPriorityErr := io.ReadUint8(8)
+            if _alarmPriorityErr != nil {
+                return nil, errors.New("Error parsing 'alarmPriority' field " + _alarmPriorityErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(alarmPriority)
+
+            // Simple Field (applicationArea)
+            applicationArea, _applicationAreaErr := io.ReadUint8(8)
+            if _applicationAreaErr != nil {
+                return nil, errors.New("Error parsing 'applicationArea' field " + _applicationAreaErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(applicationArea)
+
+            // Simple Field (errorClass)
+            errorClass, _errorClassErr := io.ReadUint8(8)
+            if _errorClassErr != nil {
+                return nil, errors.New("Error parsing 'errorClass' field " + _errorClassErr.Error())
+            }
+            _map["Struct"] = values.NewPlcUSINT(errorClass)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(4); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (errorcodeSup)
+            errorcodeSup, _errorcodeSupErr := io.ReadBit()
+            if _errorcodeSupErr != nil {
+                return nil, errors.New("Error parsing 'errorcodeSup' field " + _errorcodeSupErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(errorcodeSup)
+
+            // Simple Field (alarmtextSup)
+            alarmtextSup, _alarmtextSupErr := io.ReadBit()
+            if _alarmtextSupErr != nil {
+                return nil, errors.New("Error parsing 'alarmtextSup' field " + _alarmtextSupErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(alarmtextSup)
+
+            // Simple Field (timestampSup)
+            timestampSup, _timestampSupErr := io.ReadBit()
+            if _timestampSupErr != nil {
+                return nil, errors.New("Error parsing 'timestampSup' field " + _timestampSupErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(timestampSup)
+
+            // Simple Field (ackSup)
+            ackSup, _ackSupErr := io.ReadBit()
+            if _ackSupErr != nil {
+                return nil, errors.New("Error parsing 'ackSup' field " + _ackSupErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(ackSup)
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(5); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (locked)
+            locked, _lockedErr := io.ReadBit()
+            if _lockedErr != nil {
+                return nil, errors.New("Error parsing 'locked' field " + _lockedErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(locked)
+
+            // Simple Field (alarmunack)
+            alarmunack, _alarmunackErr := io.ReadBit()
+            if _alarmunackErr != nil {
+                return nil, errors.New("Error parsing 'alarmunack' field " + _alarmunackErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(alarmunack)
+
+            // Simple Field (inalarm)
+            inalarm, _inalarmErr := io.ReadBit()
+            if _inalarmErr != nil {
+                return nil, errors.New("Error parsing 'inalarm' field " + _inalarmErr.Error())
+            }
+            _map["Struct"] = values.NewPlcBOOL(inalarm)
+            return values.NewPlcStruct(_map), nil
+        case propertyType == KnxPropertyDataType_PDT_BINARY_INFORMATION: // BOOL
+
+            // Reserved Field (Just skip the bytes)
+            if _, _err := io.ReadUint8(7); _err != nil {
+                return nil, errors.New("Error parsing reserved field " + _err.Error())
+            }
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadBit()
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcBOOL(value), nil
+        case propertyType == KnxPropertyDataType_PDT_BITSET8: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((8)); i++ {
+                _item, _itemErr := io.ReadBit()
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcBOOL(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_BITSET16: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int((16)); i++ {
+                _item, _itemErr := io.ReadBit()
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcBOOL(_item))
+            }
+            return values.NewPlcList(value), nil
+        case propertyType == KnxPropertyDataType_PDT_ENUM8: // USINT
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadUint8(8)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcUSINT(value), nil
+        case propertyType == KnxPropertyDataType_PDT_SCALING: // USINT
+
+            // Simple Field (value)
+            value, _valueErr := io.ReadUint8(8)
+            if _valueErr != nil {
+                return nil, errors.New("Error parsing 'value' field " + _valueErr.Error())
+            }
+            return values.NewPlcUSINT(value), nil
+        default: // List
+
+            // Array Field (value)
+            var value []api.PlcValue
+            for i := 0; i < int(dataLength); i++ {
+                _item, _itemErr := io.ReadUint8(8)
+                if _itemErr != nil {
+                    return nil, errors.New("Error parsing 'value' field " + _itemErr.Error())
+                }
+                value = append(value, values.NewPlcUSINT(_item))
+            }
+            return values.NewPlcList(value), nil
+    }
+    return nil, errors.New("unsupported type")
+}
+
+func KnxPropertySerialize(io *utils.WriteBuffer, value api.PlcValue, propertyType KnxPropertyDataType, dataLength uint8) error {
+    switch {
+        case propertyType == KnxPropertyDataType_PDT_CONTROL: // BOOL
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(7, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (value)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_CHAR: // SINT
+
+            // Simple Field (value)
+            if _err := io.WriteInt8(8, value.GetInt8()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_UNSIGNED_CHAR: // USINT
+
+            // Simple Field (value)
+            if _err := io.WriteUint8(8, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_INT: // INT
+
+            // Simple Field (value)
+            if _err := io.WriteInt16(16, value.GetInt16()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_UNSIGNED_INT: // UINT
+
+            // Simple Field (value)
+            if _err := io.WriteUint16(16, value.GetUint16()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_KNX_FLOAT: // REAL
+
+            // Simple Field (value)
+            if _err := io.WriteFloat32(16, value.GetFloat32()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_DATE: // Struct
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(3, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (dayOfMonth)
+            if _err := io.WriteUint8(5, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'dayOfMonth' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(4, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (month)
+            if _err := io.WriteUint8(4, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'month' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(1, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (year)
+            if _err := io.WriteUint8(7, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'year' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_TIME: // Struct
+
+            // Simple Field (day)
+            if _err := io.WriteUint8(3, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'day' field " + _err.Error())
+            }
+
+            // Simple Field (hour)
+            if _err := io.WriteUint8(5, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'hour' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(2, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (minutes)
+            if _err := io.WriteUint8(6, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'minutes' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(2, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (seconds)
+            if _err := io.WriteUint8(6, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'seconds' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_LONG: // DINT
+
+            // Simple Field (value)
+            if _err := io.WriteInt32(32, value.GetInt32()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_UNSIGNED_LONG: // UDINT
+
+            // Simple Field (value)
+            if _err := io.WriteUint32(32, value.GetUint32()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_FLOAT: // REAL
+
+            // Simple Field (value)
+            if _err := io.WriteFloat32(32, value.GetFloat32()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_DOUBLE: // LREAL
+
+            // Simple Field (value)
+            if _err := io.WriteFloat64(64, value.GetFloat64()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_CHAR_BLOCK: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((10)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_POLL_GROUP_SETTINGS: // Struct
+
+            // Array Field (groupAddress)
+            for i := uint32(0); i < uint32((2)); i++ {
+                groupAddress := value.GetValue("groupAddress")
+                _itemErr := io.WriteUint8(8, groupAddress.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+
+            // Simple Field (disable)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'disable' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(3, uint8(0x0)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (pollingSoftNr)
+            if _err := io.WriteUint8(4, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'pollingSoftNr' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_SHORT_CHAR_BLOCK: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((5)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_DATE_TIME: // Struct
+
+            // Simple Field (year)
+            if _err := io.WriteUint8(8, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'year' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(4, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (month)
+            if _err := io.WriteUint8(4, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'month' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(3, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (dayofmonth)
+            if _err := io.WriteUint8(5, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'dayofmonth' field " + _err.Error())
+            }
+
+            // Simple Field (dayofweek)
+            if _err := io.WriteUint8(3, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'dayofweek' field " + _err.Error())
+            }
+
+            // Simple Field (hourofday)
+            if _err := io.WriteUint8(5, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'hourofday' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(2, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (minutes)
+            if _err := io.WriteUint8(6, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'minutes' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(2, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (seconds)
+            if _err := io.WriteUint8(6, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'seconds' field " + _err.Error())
+            }
+
+            // Simple Field (fault)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'fault' field " + _err.Error())
+            }
+
+            // Simple Field (workingDay)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'workingDay' field " + _err.Error())
+            }
+
+            // Simple Field (noWd)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'noWd' field " + _err.Error())
+            }
+
+            // Simple Field (noYear)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'noYear' field " + _err.Error())
+            }
+
+            // Simple Field (noDate)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'noDate' field " + _err.Error())
+            }
+
+            // Simple Field (noDayOfWeek)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'noDayOfWeek' field " + _err.Error())
+            }
+
+            // Simple Field (noTime)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'noTime' field " + _err.Error())
+            }
+
+            // Simple Field (standardSummerTime)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'standardSummerTime' field " + _err.Error())
+            }
+
+            // Simple Field (qualityOfClock)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'qualityOfClock' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(7, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_01: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((1)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_02: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((2)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_03: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((3)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_04: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((4)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_05: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((5)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_06: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((6)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_07: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((7)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_08: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((8)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_09: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((9)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_10: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((10)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_11: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((11)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_12: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((12)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_13: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((13)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_14: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((14)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_15: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((15)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_16: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((16)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_17: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((17)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_18: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((18)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_19: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((19)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_GENERIC_20: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((20)); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_VERSION: // Struct
+
+            // Simple Field (magicNumber)
+            if _err := io.WriteUint8(5, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'magicNumber' field " + _err.Error())
+            }
+
+            // Simple Field (versionNumber)
+            if _err := io.WriteUint8(5, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'versionNumber' field " + _err.Error())
+            }
+
+            // Simple Field (revisionNumber)
+            if _err := io.WriteUint8(6, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'revisionNumber' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_ALARM_INFO: // Struct
+
+            // Simple Field (logNumber)
+            if _err := io.WriteUint8(8, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'logNumber' field " + _err.Error())
+            }
+
+            // Simple Field (alarmPriority)
+            if _err := io.WriteUint8(8, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'alarmPriority' field " + _err.Error())
+            }
+
+            // Simple Field (applicationArea)
+            if _err := io.WriteUint8(8, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'applicationArea' field " + _err.Error())
+            }
+
+            // Simple Field (errorClass)
+            if _err := io.WriteUint8(8, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'errorClass' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(4, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (errorcodeSup)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'errorcodeSup' field " + _err.Error())
+            }
+
+            // Simple Field (alarmtextSup)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'alarmtextSup' field " + _err.Error())
+            }
+
+            // Simple Field (timestampSup)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'timestampSup' field " + _err.Error())
+            }
+
+            // Simple Field (ackSup)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'ackSup' field " + _err.Error())
+            }
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(5, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (locked)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'locked' field " + _err.Error())
+            }
+
+            // Simple Field (alarmunack)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'alarmunack' field " + _err.Error())
+            }
+
+            // Simple Field (inalarm)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'inalarm' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_BINARY_INFORMATION: // BOOL
+
+            // Reserved Field (Just skip the bytes)
+            if _err := io.WriteUint8(7, uint8(0x00)); _err != nil {
+                return errors.New("Error serializing reserved field " + _err.Error())
+            }
+
+            // Simple Field (value)
+            if _err := io.WriteBit(value.GetBool()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_BITSET8: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((8)); i++ {
+                _itemErr := io.WriteBit(value.GetIndex(i).GetBool())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_BITSET16: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32((16)); i++ {
+                _itemErr := io.WriteBit(value.GetIndex(i).GetBool())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+        case propertyType == KnxPropertyDataType_PDT_ENUM8: // USINT
+
+            // Simple Field (value)
+            if _err := io.WriteUint8(8, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        case propertyType == KnxPropertyDataType_PDT_SCALING: // USINT
+
+            // Simple Field (value)
+            if _err := io.WriteUint8(8, value.GetUint8()); _err != nil {
+                return errors.New("Error serializing 'value' field " + _err.Error())
+            }
+        default: // List
+
+            // Array Field (value)
+            for i := uint32(0); i < uint32(dataLength); i++ {
+                _itemErr := io.WriteUint8(8, value.GetIndex(i).GetUint8())
+                if _itemErr != nil {
+                    return errors.New("Error serializing 'value' field " + _itemErr.Error())
+                }
+            }
+    }
+    return nil
+}
+
diff --git a/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec b/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec
index ae14310..916e086 100644
--- a/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec
+++ b/protocols/knxnetip/src/main/resources/protocols/knxnetip/knxnetip.mspec
@@ -759,4 +759,210 @@
 [type 'ChannelInformation'
     [simple uint 3  'numChannels']
     [simple uint 13 'channelCode']
+]
+
+// Switch constants taken from the generated KnxPropertyDataType type
+// Representation looked up in:
+// - 03_05_01 Resources v01.09.03 AS.pdf
+// - 03_07_03 Standardized Identifier Tables v01.03.01 AS.pdf
+// - 03_07_02 Datapoint Types v01.08.02 AS.pdf
+[dataIo 'KnxProperty' [KnxPropertyDataType 'propertyType', uint 8 'dataLength']
+    [typeSwitch 'propertyType'
+        ['KnxPropertyDataType.PDT_CONTROL' BOOL
+            [reserved uint 7        '0x00']
+            [simple   bit           'value']
+        ]
+        ['KnxPropertyDataType.PDT_CHAR' SINT
+            [simple   int 8         'value']
+        ]
+        ['KnxPropertyDataType.PDT_UNSIGNED_CHAR' USINT
+            [simple   uint 8        'value']
+        ]
+        ['KnxPropertyDataType.PDT_INT' INT
+            [simple   int 16        'value']
+        ]
+        ['KnxPropertyDataType.PDT_UNSIGNED_INT' UINT
+            [simple   uint 16       'value']
+        ]
+        ['KnxPropertyDataType.PDT_KNX_FLOAT' REAL
+            [simple   float 4.11    'value']
+        ]
+        ['KnxPropertyDataType.PDT_DATE' Struct
+            [reserved uint 3 '0x00']
+            [simple uint 5 'dayOfMonth']
+            [reserved uint 4 '0x00']
+            [simple uint 4 'month']
+            [reserved uint 1 '0x00']
+            [simple uint 7 'year']        ]
+        ['KnxPropertyDataType.PDT_TIME' Struct
+            [simple uint 3 'day']
+            [simple uint 5 'hour']
+            [reserved uint 2 '0x00']
+            [simple uint 6 'minutes']
+            [reserved uint 2 '0x00']
+            [simple uint 6 'seconds']
+        ]
+        ['KnxPropertyDataType.PDT_LONG' DINT
+            [simple   int 32        'value']
+        ]
+        ['KnxPropertyDataType.PDT_UNSIGNED_LONG' UDINT
+            [simple   uint 32       'value']
+        ]
+        ['KnxPropertyDataType.PDT_FLOAT' REAL
+            [simple   float 8.23    'value']
+        ]
+        ['KnxPropertyDataType.PDT_DOUBLE' LREAL
+            [simple   float 11.52   'value']
+        ]
+        ['KnxPropertyDataType.PDT_CHAR_BLOCK' List
+            [array uint 8           'value' count '10']
+        ]
+        ['KnxPropertyDataType.PDT_POLL_GROUP_SETTINGS' Struct
+            [array    uint 8        'groupAddress' count '2']
+            [simple   bit           'disable']
+            [reserved uint 3        '0x0']
+            [simple   uint 4        'pollingSoftNr']
+        ]
+        ['KnxPropertyDataType.PDT_SHORT_CHAR_BLOCK' List
+            [array uint 8           'value' count '5']
+        ]
+        ['KnxPropertyDataType.PDT_DATE_TIME' Struct
+            [simple uint 8 'year']
+            [reserved uint 4 '0x00']
+            [simple uint 4 'month']
+            [reserved uint 3 '0x00']
+            [simple uint 5 'dayofmonth']
+            [simple uint 3 'dayofweek']
+            [simple uint 5 'hourofday']
+            [reserved uint 2 '0x00']
+            [simple uint 6 'minutes']
+            [reserved uint 2 '0x00']
+            [simple uint 6 'seconds']
+            [simple bit 'fault']
+            [simple bit 'workingDay']
+            [simple bit 'noWd']
+            [simple bit 'noYear']
+            [simple bit 'noDate']
+            [simple bit 'noDayOfWeek']
+            [simple bit 'noTime']
+            [simple bit 'standardSummerTime']
+            [simple bit 'qualityOfClock']
+            [reserved uint 7 '0x00']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_01' List
+            [array uint 8           'value' count '1']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_02' List
+            [array uint 8           'value' count '2']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_03' List
+            [array uint 8           'value' count '3']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_04' List
+            [array uint 8           'value' count '4']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_05' List
+            [array uint 8           'value' count '5']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_06' List
+            [array uint 8           'value' count '6']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_07' List
+            [array uint 8           'value' count '7']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_08' List
+            [array uint 8           'value' count '8']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_09' List
+            [array uint 8           'value' count '9']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_10' List
+            [array uint 8           'value' count '10']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_11' List
+            [array uint 8           'value' count '11']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_12' List
+            [array uint 8           'value' count '12']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_13' List
+            [array uint 8           'value' count '13']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_14' List
+            [array uint 8           'value' count '14']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_15' List
+            [array uint 8           'value' count '15']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_16' List
+            [array uint 8           'value' count '16']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_17' List
+            [array uint 8           'value' count '17']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_18' List
+            [array uint 8           'value' count '18']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_19' List
+            [array uint 8           'value' count '19']
+        ]
+        ['KnxPropertyDataType.PDT_GENERIC_20' List
+            [array uint 8           'value' count '20']
+        ]
+        // Defaults to PDT_VARIABLE_LENGTH
+        //['KnxPropertyDataType.PDT_UTF_8'
+        //]
+        ['KnxPropertyDataType.PDT_VERSION' Struct
+            [simple uint 5 'magicNumber']
+            [simple uint 5 'versionNumber']
+            [simple uint 6 'revisionNumber']
+        ]
+        ['KnxPropertyDataType.PDT_ALARM_INFO' Struct
+            [simple uint 8 'logNumber']
+            [simple uint 8 'alarmPriority']
+            [simple uint 8 'applicationArea']
+            [simple uint 8 'errorClass']
+            [reserved uint 4 '0x00']
+            [simple bit 'errorcodeSup']
+            [simple bit 'alarmtextSup']
+            [simple bit 'timestampSup']
+            [simple bit 'ackSup']
+            [reserved uint 5 '0x00']
+            [simple bit 'locked']
+            [simple bit 'alarmunack']
+            [simple bit 'inalarm']
+        ]
+        ['KnxPropertyDataType.PDT_BINARY_INFORMATION' BOOL
+            [reserved uint 7        '0x00']
+            [simple   bit           'value']
+        ]
+        ['KnxPropertyDataType.PDT_BITSET8' List
+            [array    bit           'value' count '8']
+        ]
+        ['KnxPropertyDataType.PDT_BITSET16' List
+            [array    bit           'value' count '16']
+        ]
+        ['KnxPropertyDataType.PDT_ENUM8' USINT
+            [simple uint 8 'value']
+        ]
+        ['KnxPropertyDataType.PDT_SCALING' USINT
+            [simple uint 8 'value']
+        ]
+        // Defaults to PDT_VARIABLE_LENGTH
+        //['KnxPropertyDataType.PDT_NE_VL'
+        //]
+        // Defaults to PDT_VARIABLE_LENGTH
+        //['KnxPropertyDataType.PDT_NE_FL'
+        //]
+        // Defaults to PDT_VARIABLE_LENGTH
+        //['KnxPropertyDataType.PDT_FUNCTION'
+        //]
+        // Defaults to PDT_VARIABLE_LENGTH
+        //['KnxPropertyDataType.PDT_ESCAPE'
+        //]
+        // 'KnxPropertyDataType.PDT_VARIABLE_LENGTH' == Catch all
+        [ List [uint 8 'dataLength']
+            [array uint 8 'value' count 'dataLength']
+        ]
+    ]
 ]
\ No newline at end of file