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/06 11:05:50 UTC

[plc4x] branch develop updated: - Implemented a first mostly working version of active property reading in KNX

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

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


The following commit(s) were added to refs/heads/develop by this push:
     new 4a8c91a  - Implemented a first mostly working version of active property reading in KNX
4a8c91a is described below

commit 4a8c91afe7daefe35e2982e3bd66f2dd4635d3d3
Author: cdutz <ch...@c-ware.de>
AuthorDate: Wed Jan 6 12:05:15 2021 +0100

    - Implemented a first mostly working version of active property reading in KNX
---
 plc4go/cmd/main/drivers/knxnetip_test.go           |   55 +-
 .../cmd/main/drivers/tests/knx_discovery_test.go   | 1222 ++++++++++----------
 plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go |   62 +-
 .../internal/plc4go/knxnetip/KnxNetIpConnection.go |   22 +-
 plc4go/internal/plc4go/knxnetip/KnxNetIpField.go   |  476 ++++----
 .../plc4go/knxnetip/KnxNetIpMessageCodec.go        |  292 ++---
 plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go  |  530 +++++++--
 plc4go/internal/plc4go/knxnetip/Utils.go           |   42 +
 8 files changed, 1514 insertions(+), 1187 deletions(-)

diff --git a/plc4go/cmd/main/drivers/knxnetip_test.go b/plc4go/cmd/main/drivers/knxnetip_test.go
index 9865993..01ee116 100644
--- a/plc4go/cmd/main/drivers/knxnetip_test.go
+++ b/plc4go/cmd/main/drivers/knxnetip_test.go
@@ -142,14 +142,13 @@ func TestKnxNetIpPlc4goDiscovery(t *testing.T) {
 	}
 }
 
-func TestKnxNetIpPlc4goDriver(t *testing.T) {
+func TestKnxNetIpPlc4goGroupAddressRead(t *testing.T) {
 	driverManager := plc4go.NewPlcDriverManager()
 	driverManager.RegisterDriver(knxnetip.NewKnxNetIpDriver())
 	driverManager.RegisterTransport(udp.NewUdpTransport())
 
 	// Get a connection to a remote PLC
 	crc := driverManager.GetConnection("knxnet-ip://192.168.42.11")
-	//crc := driverManager.GetConnection("knxnet-ip://-discover-")
 
 	// Wait for the driver to connect (or not)
 	connectionResult := <-crc
@@ -167,8 +166,6 @@ func TestKnxNetIpPlc4goDriver(t *testing.T) {
 		attributes["GatewayKnxAddress"],
 		attributes["ClientKnxAddress"])
 
-	time.Sleep(time.Millisecond * 100)
-
 	// TODO: Find out why a connection-state request breaks everything ...
 	// Try to ping the remote device
 	pingResultChannel := connection.Ping()
@@ -225,35 +222,35 @@ func TestKnxNetIpPlc4goDriver(t *testing.T) {
 			}
 		}
 	}
-	/*value1 := rrr.Response.GetValue("field1")
-	  value2 := rrr.Response.GetValue("field2")
-	  fmt.Printf("\n\nResult field1: %f\n", value1.GetFloat32())
-	  fmt.Printf("\n\nResult field1: %f\n", value2.GetFloat32())
+}
+
+func TestKnxNetIpPlc4goPropertyRead(t *testing.T) {
+	driverManager := plc4go.NewPlcDriverManager()
+	driverManager.RegisterDriver(knxnetip.NewKnxNetIpDriver())
+	driverManager.RegisterTransport(udp.NewUdpTransport())
+
+	// Get a connection to a remote PLC
+	crc := driverManager.GetConnection("knxnet-ip://192.168.42.11")
 
-	  // Prepare a write-request
-	  wrb := connection.WriteRequestBuilder()
-	  wrb.AddItem("field1", "holding-register:1:REAL", 1.2345)
-	  wrb.AddItem("field2", "holding-register:3:REAL", 2.3456)
-	  writeRequest, err := rrb.Build()
-	  if err != nil {
-	      t.Errorf("error preparing read-request: %s", connectionResult.Err.Error())
-	      t.Fail()
-	      return
-	  }
+	// Wait for the driver to connect (or not)
+	connectionResult := <-crc
+	if connectionResult.Err != nil {
+		t.Errorf("error connecting to PLC: %s", connectionResult.Err.Error())
+		t.Fail()
+		return
+	}
+	connection := connectionResult.Connection
+	defer connection.Close()
 
-	  // Execute a write-request
-	  wrc := writeRequest.Execute()
+	readRequestBuilder := connection.ReadRequestBuilder()
+	readRequestBuilder.AddItem("manufacturerId", "1.1.10/0/12")
+	readRequestBuilder.AddItem("hardwareType", "1.1.10/0/78")
+	readRequest, _ := readRequestBuilder.Build()
 
-	  // Wait for the response to finish
-	  wrr := <-wrc
-	  if wrr.Err != nil {
-	      t.Errorf("error executing read-request: %s", rrr.Err.Error())
-	      t.Fail()
-	      return
-	  }
+	rrr := readRequest.Execute()
+	readResult := <-rrr
 
-	  fmt.Printf("\n\nResult field1: %d\n", wrr.Response.GetResponseCode("field1"))
-	  fmt.Printf("\n\nResult field2: %d\n", wrr.Response.GetResponseCode("field2"))*/
+	fmt.Printf("Got result %v", readResult)
 }
 
 func knxEventHandler(event apiModel.PlcSubscriptionEvent) {
diff --git a/plc4go/cmd/main/drivers/tests/knx_discovery_test.go b/plc4go/cmd/main/drivers/tests/knx_discovery_test.go
index 79c44c8..d0ab5a4 100644
--- a/plc4go/cmd/main/drivers/tests/knx_discovery_test.go
+++ b/plc4go/cmd/main/drivers/tests/knx_discovery_test.go
@@ -19,643 +19,601 @@
 package tests
 
 import (
-    "encoding/hex"
-    "errors"
-    "fmt"
-    "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
 	"github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
-    log "github.com/sirupsen/logrus"
-    "net"
-    "strconv"
-    "strings"
-    "testing"
+	log "github.com/sirupsen/logrus"
+	"net"
+	"strconv"
+	"strings"
+	"testing"
 )
 
 func TestKnxAutoDiscovery(t *testing.T) {
-    interfaces, err := net.Interfaces()
-    if err != nil {
-        log.Errorf("Error getting interefaces: %s", err.Error())
-        t.Fail()
-    }
-
-    for _, interf := range interfaces {
-        addrs, err := interf.Addrs()
-        if err != nil {
-            log.Errorf("Error getting addresses of interface: %s. Got error: %s", interf.Name, err.Error())
-            t.Fail()
-        }
-        for _, addr := range addrs {
-            var ipv4Addr net.IP
-            switch addr.(type) {
-            // If the device is configured to communicate with a subnet
-            case *net.IPNet:
-                ipv4Addr = addr.(*net.IPNet).IP.To4()
-
-            // If the device is configured for a point-to-point connection
-            case *net.IPAddr:
-                ipv4Addr = addr.(*net.IPAddr).IP.To4()
-            }
-
-            // Only if this is an IPv4 address, will we open a port for it.
-            if ipv4Addr != nil {
-                // Open a listening port on a random free port number
-                udpIpv4Addr := &net.UDPAddr{IP: ipv4Addr, Port: 0}
-
-                udpSocket, err := net.ListenUDP("udp4", udpIpv4Addr)
-                if err != nil {
-                    log.Warnf("Error creating listening port for KNX on address %s", ipv4Addr.String())
-                    continue
-                }
-
-                go func() {
-                    buf := make([]byte, 1024)
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Send the Search Request using the current network device
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    localIp := udpSocket.LocalAddr().(*net.UDPAddr).IP
-                    localPort := udpSocket.LocalAddr().(*net.UDPAddr).Port
-
-                    // Prepare the discovery packet data
-                    searchRequestMessage := model.NewSearchRequest(model.NewHPAIDiscoveryEndpoint(
-                        model.HostProtocolCode_IPV4_UDP,
-                        model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
-                        uint16(localPort)))
-                    writeBuffer := utils.NewWriteBuffer()
-                    err := searchRequestMessage.Serialize(*writeBuffer)
-                    if err != nil {
-                        panic("Failed preparing search request.")
-                    }
-
-                    // This is the multicast address and port KNX devices are supposed to listen to.
-                    destination := &net.UDPAddr{IP: net.IPv4(224, 0, 23, 12), Port: 3671}
-
-                    // Send the message
-                    _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), destination)
-                    if err != nil {
-                        panic("Failed sending search request.")
-                    }
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Wait for a response from a device that supports tunneling
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    var searchResponse *model.SearchResponse
-                    var gatewayAddr *net.UDPAddr
-                    for searchResponse == nil {
-                        // Read a new packet from the socket
-                        _, src, err := udpSocket.ReadFromUDP(buf)
-                        if err != nil {
-                            panic("Error reading from KNX UDP socket")
-                        }
-
-                        readBuffer := utils.NewReadBuffer(buf)
-                        knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
-                        if err != nil {
-                            hexEncodedPayload := hex.EncodeToString(buf)
-                            panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
-                        }
-
-                        switch knxMessage.Child.(type) {
-                        // If this is a search response and the current device supports tunneling,
-                        // we've found what we're looking for.
-                        case *model.SearchResponse:
-                            curSearchResponse := model.CastSearchResponse(knxMessage)
-                            for _, serviceId := range curSearchResponse.DibSuppSvcFamilies.ServiceIds {
-                                switch (*serviceId).Child.(type) {
-                                case *model.KnxNetIpTunneling:
-                                    searchResponse = curSearchResponse
-                                    gatewayAddr = src
-                                    break
-                                }
-                            }
-
-                        // Just ACK any incoming tunneling requests
-                        case *model.TunnelingRequest:
-                            tunnelingRequest := model.CastTunnelingRequest(knxMessage)
-
-                            tunnelingResponse := model.NewTunnelingResponse(
-                                model.NewTunnelingResponseDataBlock(
-                                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
-                                    tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
-                                    model.Status_NO_ERROR))
-                            writeBuffer := utils.NewWriteBuffer()
-                            err = tunnelingResponse.Serialize(*writeBuffer)
-                            if err != nil {
-                                panic("Failed preparing tunneling response.")
-                            }
-
-                            // Send the message
-                            _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
-                            if err != nil {
-                                panic("Failed sending tunneling response.")
-                            }
-                        }
-                    }
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Send a connection request to the device we just found
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    connectionRequestMessage := model.NewConnectionRequest(
-                        model.NewHPAIDiscoveryEndpoint(
-                            model.HostProtocolCode_IPV4_UDP,
-                            model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
-                            uint16(localPort)),
-                        model.NewHPAIDataEndpoint(
-                            model.HostProtocolCode_IPV4_UDP,
-                            model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
-                            uint16(localPort)),
-                        model.NewConnectionRequestInformationTunnelConnection(model.KnxLayer_TUNNEL_LINK_LAYER))
-                    writeBuffer = utils.NewWriteBuffer()
-                    err = connectionRequestMessage.Serialize(*writeBuffer)
-                    if err != nil {
-                        panic("Failed preparing connection request.")
-                    }
-
-                    // Send the message
-                    _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Wait for a connection response
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    var communicationChannelId uint8
-                    for communicationChannelId == 0 {
-                        // Read a new packet from the socket
-                        _, src, err := udpSocket.ReadFromUDP(buf)
-                        if err != nil {
-                            panic("Error reading from KNX UDP socket")
-                        }
-
-                        readBuffer := utils.NewReadBuffer(buf)
-                        knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
-                        if err != nil {
-                            hexEncodedPayload := hex.EncodeToString(buf)
-                            panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
-                        }
-
-                        switch knxMessage.Child.(type) {
-                        case *model.ConnectionResponse:
-                            connectionResponse := model.CastConnectionResponse(knxMessage)
-                            if connectionResponse.Status == model.Status_NO_ERROR {
-                                communicationChannelId = connectionResponse.CommunicationChannelId
-                            } else {
-                                panic("Got an error while connecting")
-                            }
-
-                        // Just ACK any incoming tunneling requests
-                        case *model.TunnelingRequest:
-                            tunnelingRequest := model.CastTunnelingRequest(knxMessage)
-
-                            tunnelingResponse := model.NewTunnelingResponse(
-                                model.NewTunnelingResponseDataBlock(
-                                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
-                                    tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
-                                    model.Status_NO_ERROR))
-                            writeBuffer := utils.NewWriteBuffer()
-                            err = tunnelingResponse.Serialize(*writeBuffer)
-                            if err != nil {
-                                panic("Failed preparing tunneling response.")
-                            }
-
-                            // Send the message
-                            _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
-                            if err != nil {
-                                panic("Failed sending tunneling response.")
-                            }
-                        }
-                    }
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Send a config connection request
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    /*configConnectionRequestMessage := model.NewConnectionRequest(
-                        model.NewHPAIDiscoveryEndpoint(
-                            model.HostProtocolCode_IPV4_UDP,
-                            model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
-                            uint16(localPort)),
-                        model.NewHPAIDataEndpoint(
-                            model.HostProtocolCode_IPV4_UDP,
-                            model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
-                            uint16(localPort)),
-                        model.NewConnectionRequestInformationDeviceManagement())
-                    writeBuffer = utils.NewWriteBuffer()
-                    err = configConnectionRequestMessage.Serialize(*writeBuffer)
-                    if err != nil {
-                        panic("Failed preparing connection request.")
-                    }
-
-                    // Send the message
-                    _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Wait for a config connection response
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    var configCommunicationChannelId uint8
-                    for configCommunicationChannelId == 0 {
-                        // Read a new packet from the socket
-                        _, src, err := udpSocket.ReadFromUDP(buf)
-                        if err != nil {
-                            panic("Error reading from KNX UDP socket")
-                        }
-
-                        readBuffer := utils.NewReadBuffer(buf)
-                        knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
-                        if err != nil {
-                            hexEncodedPayload := hex.EncodeToString(buf)
-                            panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
-                        }
-
-                        switch knxMessage.Child.(type) {
-                        case *model.ConnectionResponse:
-                            connectionResponse := model.CastConnectionResponse(knxMessage)
-                            if connectionResponse.Status == model.Status_NO_ERROR {
-                                configCommunicationChannelId = connectionResponse.CommunicationChannelId
-                            } else {
-                                panic("Got an error while connecting")
-                            }
-
-                        // Just ACK any incoming tunneling requests
-                        case *model.TunnelingRequest:
-                            tunnelingRequest := model.CastTunnelingRequest(knxMessage)
-
-                            tunnelingResponse := model.NewTunnelingResponse(
-                                model.NewTunnelingResponseDataBlock(
-                                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
-                                    tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
-                                    model.Status_NO_ERROR))
-                            writeBuffer := utils.NewWriteBuffer()
-                            err = tunnelingResponse.Serialize(*writeBuffer)
-                            if err != nil {
-                                panic("Failed preparing tunneling response.")
-                            }
-
-                            // Send the message
-                            _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
-                            if err != nil {
-                                panic("Failed sending tunneling response.")
-                            }
-                        }
-                    }
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Send a prop-read request for PID_MEDIUM_TYPE
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    mediumTypePropRequestMessage := model.NewDeviceConfigurationRequest(
-                        model.NewDeviceConfigurationRequestDataBlock(configCommunicationChannelId, uint8(0)),
-                        model.NewMPropReadReq(uint16(8), uint8(1), uint8(51), uint8(1), uint16(1)))
-                    writeBuffer = utils.NewWriteBuffer()
-                    err = mediumTypePropRequestMessage.Serialize(*writeBuffer)
-                    if err != nil {
-                        panic("Failed preparing config disconnect request.")
-                    }
-
-                    // Send the message
-                    _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Wait for a prop read response
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    var mediumType uint16
-                    for mediumType == 0 {
-                        // Read a new packet from the socket
-                        _, src, err := udpSocket.ReadFromUDP(buf)
-                        if err != nil {
-                            panic("Error reading from KNX UDP socket")
-                        }
-
-                        readBuffer := utils.NewReadBuffer(buf)
-                        knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
-                        if err != nil {
-                            hexEncodedPayload := hex.EncodeToString(buf)
-                            panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
-                        }
-
-                        switch knxMessage.Child.(type) {
-                        case *model.DeviceConfigurationRequest:
-                            deviceConfigurationRequest := model.CastDeviceConfigurationRequest(knxMessage)
-                            switch deviceConfigurationRequest.Cemi.Child.(type) {
-                            case *model.MPropReadCon:
-                                readCon := model.CastMPropReadCon(deviceConfigurationRequest.Cemi)
-                                // TODO: This should be renamed to "Data"
-                                mediumType = readCon.Unknown
-
-                                // Send and ACK for this response
-                                tunnelingResponse := model.NewTunnelingResponse(
-                                    model.NewTunnelingResponseDataBlock(
-                                        deviceConfigurationRequest.DeviceConfigurationRequestDataBlock.CommunicationChannelId,
-                                        deviceConfigurationRequest.DeviceConfigurationRequestDataBlock.SequenceCounter,
-                                        model.Status_NO_ERROR))
-                                writeBuffer := utils.NewWriteBuffer()
-                                err = tunnelingResponse.Serialize(*writeBuffer)
-                                if err != nil {
-                                    panic("Failed preparing tunneling response.")
-                                }
-
-                                // Send the message
-                                _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
-                                if err != nil {
-                                    panic("Failed sending tunneling response.")
-                                }
-                            }
-
-                        // Just ACK any incoming tunneling requests
-                        case *model.TunnelingRequest:
-                            tunnelingRequest := model.CastTunnelingRequest(knxMessage)
-
-                            tunnelingResponse := model.NewTunnelingResponse(
-                                model.NewTunnelingResponseDataBlock(
-                                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
-                                    tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
-                                    model.Status_NO_ERROR))
-                            writeBuffer := utils.NewWriteBuffer()
-                            err = tunnelingResponse.Serialize(*writeBuffer)
-                            if err != nil {
-                                panic("Failed preparing tunneling response.")
-                            }
-
-                            // Send the message
-                            _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
-                            if err != nil {
-                                panic("Failed sending tunneling response.")
-                            }
-                        }
-                    }
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Send a prop-read request for PID_MAX_APDULENGTH
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    maxApdulengthPropRequestMessage := model.NewDeviceConfigurationRequest(
-                        model.NewDeviceConfigurationRequestDataBlock(configCommunicationChannelId, uint8(1)),
-                        model.NewMPropReadReq(uint16(0), uint8(1), uint8(56), uint8(1), uint16(1)))
-                    writeBuffer = utils.NewWriteBuffer()
-                    err = maxApdulengthPropRequestMessage.Serialize(*writeBuffer)
-                    if err != nil {
-                        panic("Failed preparing config disconnect request.")
-                    }
-
-                    // Send the message
-                    _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Wait for a prop read response
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    var maxApdulength uint16
-                    for maxApdulength == 0 {
-                        // Read a new packet from the socket
-                        _, src, err := udpSocket.ReadFromUDP(buf)
-                        if err != nil {
-                            panic("Error reading from KNX UDP socket")
-                        }
-
-                        readBuffer := utils.NewReadBuffer(buf)
-                        knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
-                        if err != nil {
-                            hexEncodedPayload := hex.EncodeToString(buf)
-                            panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
-                        }
-
-                        switch knxMessage.Child.(type) {
-                        case *model.DeviceConfigurationRequest:
-                            deviceConfigurationRequest := model.CastDeviceConfigurationRequest(knxMessage)
-                            switch deviceConfigurationRequest.Cemi.Child.(type) {
-                            case *model.MPropReadCon:
-                                readCon := model.CastMPropReadCon(deviceConfigurationRequest.Cemi)
-                                // TODO: This should be renamed to "Data"
-                                maxApdulength = readCon.Unknown
-
-                                // Send and ACK for this response
-                                tunnelingResponse := model.NewTunnelingResponse(
-                                    model.NewTunnelingResponseDataBlock(
-                                        deviceConfigurationRequest.DeviceConfigurationRequestDataBlock.CommunicationChannelId,
-                                        deviceConfigurationRequest.DeviceConfigurationRequestDataBlock.SequenceCounter,
-                                        model.Status_NO_ERROR))
-                                writeBuffer := utils.NewWriteBuffer()
-                                err = tunnelingResponse.Serialize(*writeBuffer)
-                                if err != nil {
-                                    panic("Failed preparing tunneling response.")
-                                }
-
-                                // Send the message
-                                _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
-                                if err != nil {
-                                    panic("Failed sending tunneling response.")
-                                }
-                            }
-
-                        // Just ACK any incoming tunneling requests
-                        case *model.TunnelingRequest:
-                            tunnelingRequest := model.CastTunnelingRequest(knxMessage)
-
-                            tunnelingResponse := model.NewTunnelingResponse(
-                                model.NewTunnelingResponseDataBlock(
-                                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
-                                    tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
-                                    model.Status_NO_ERROR))
-                            writeBuffer := utils.NewWriteBuffer()
-                            err = tunnelingResponse.Serialize(*writeBuffer)
-                            if err != nil {
-                                panic("Failed preparing tunneling response.")
-                            }
-
-                            // Send the message
-                            _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
-                            if err != nil {
-                                panic("Failed sending tunneling response.")
-                            }
-                        }
-                    }
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Send a config connection disconnect request
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    configConnectionDisconnectRequestMessage := model.NewDisconnectRequest(
-                        configCommunicationChannelId,
-                        model.NewHPAIControlEndpoint(
-                            model.HostProtocolCode_IPV4_UDP,
-                            model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
-                            uint16(localPort)))
-                    writeBuffer = utils.NewWriteBuffer()
-                    err = configConnectionDisconnectRequestMessage.Serialize(*writeBuffer)
-                    if err != nil {
-                        panic("Failed preparing config disconnect request.")
-                    }
-
-                    // Send the message
-                    _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Wait for a config disconnect response
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    configDisconnected := false
-                    for !configDisconnected {
-                        // Read a new packet from the socket
-                        _, src, err := udpSocket.ReadFromUDP(buf)
-                        if err != nil {
-                            panic("Error reading from KNX UDP socket")
-                        }
-
-                        readBuffer := utils.NewReadBuffer(buf)
-                        knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
-                        if err != nil {
-                            hexEncodedPayload := hex.EncodeToString(buf)
-                            panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
-                        }
-
-                        switch knxMessage.Child.(type) {
-                        case *model.DisconnectResponse:
-                            disconnectResponse := model.CastDisconnectResponse(knxMessage)
-                            if disconnectResponse.Status == model.Status_NO_ERROR {
-                                configDisconnected = true
-                            } else {
-                                panic("Got an error while connecting")
-                            }
-
-                        // Just ACK any incoming tunneling requests
-                        case *model.TunnelingRequest:
-                            tunnelingRequest := model.CastTunnelingRequest(knxMessage)
-
-                            tunnelingResponse := model.NewTunnelingResponse(
-                                model.NewTunnelingResponseDataBlock(
-                                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
-                                    tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
-                                    model.Status_NO_ERROR))
-                            writeBuffer := utils.NewWriteBuffer()
-                            err = tunnelingResponse.Serialize(*writeBuffer)
-                            if err != nil {
-                                panic("Failed preparing tunneling response.")
-                            }
-
-                            // Send the message
-                            _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
-                            if err != nil {
-                                panic("Failed sending tunneling response.")
-                            }
-                        }
-                    }*/
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Send a device connection request to KNX address 1.1.10
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    targetKnxAddress, err := ParseKnxAddressString("1.1.10")
-                    if err != nil {
-                        panic("Failed preparing discovery request.")
-                    }
-                    sourceAddress := &model.KnxAddress{
-                        MainGroup:   0,
-                        MiddleGroup: 0,
-                        SubGroup:    0,
-                    }
-                    controlType := model.ControlType_CONNECT
-                    deviceConnectionRequest := model.NewTunnelingRequest(
-                        model.NewTunnelingRequestDataBlock(communicationChannelId, 0),
-                        model.NewLDataReq(0, nil,
-                            model.NewLDataFrameDataExt(false, 6, uint8(0),
-                                sourceAddress, targetKnxAddress, uint8(0), true, false,
-                                uint8(0), &controlType, nil, nil, nil, nil,
-                                false, model.CEMIPriority_SYSTEM, false, false)))
-                    writeBuffer = utils.NewWriteBuffer()
-                    err = deviceConnectionRequest.Serialize(*writeBuffer)
-                    if err != nil {
-                        panic("Failed preparing device connection request.")
-                    }
-
-                    // Send the message
-                    _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
-                    if err != nil {
-                        panic("Failed sending device connection request.")
-                    }
-
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-                    // Wait for a device connection response
-                    ////////////////////////////////////////////////////////////////////////////////////////////////////
-
-                    for {
-                        // Read a new packet from the socket
-                        _, src, err := udpSocket.ReadFromUDP(buf)
-                        if err != nil {
-                            panic("Error reading from KNX UDP socket")
-                        }
-
-                        readBuffer := utils.NewReadBuffer(buf)
-                        knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
-                        if err != nil {
-                            hexEncodedPayload := hex.EncodeToString(buf)
-                            panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
-                        }
-
-                        switch knxMessage.Child.(type) {
-                        case *model.TunnelingRequest:
-                            tunnelingRequest := model.CastTunnelingRequest(knxMessage)
-
-                            tunnelingResponse := model.NewTunnelingResponse(
-                                model.NewTunnelingResponseDataBlock(
-                                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
-                                    tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
-                                    model.Status_NO_ERROR))
-                            writeBuffer := utils.NewWriteBuffer()
-                            err = tunnelingResponse.Serialize(*writeBuffer)
-                            if err != nil {
-                                panic("Failed preparing tunneling response.")
-                            }
-
-                            // Send the message
-                            _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
-                            if err != nil {
-                                panic("Failed sending tunneling response.")
-                            }
-                        }
-                    }
-                }()
-            }
-        }
-    }
+	interfaces, err := net.Interfaces()
+	if err != nil {
+		log.Errorf("Error getting interefaces: %s", err.Error())
+		t.Fail()
+	}
+
+	for _, interf := range interfaces {
+		addrs, err := interf.Addrs()
+		if err != nil {
+			log.Errorf("Error getting addresses of interface: %s. Got error: %s", interf.Name, err.Error())
+			t.Fail()
+		}
+		for _, addr := range addrs {
+			var ipv4Addr net.IP
+			switch addr.(type) {
+			// If the device is configured to communicate with a subnet
+			case *net.IPNet:
+				ipv4Addr = addr.(*net.IPNet).IP.To4()
+
+			// If the device is configured for a point-to-point connection
+			case *net.IPAddr:
+				ipv4Addr = addr.(*net.IPAddr).IP.To4()
+			}
+
+			// Only if this is an IPv4 address, will we open a port for it.
+			if ipv4Addr != nil {
+				// Open a listening port on a random free port number
+				udpIpv4Addr := &net.UDPAddr{IP: ipv4Addr, Port: 0}
+
+				udpSocket, err := net.ListenUDP("udp4", udpIpv4Addr)
+				if err != nil {
+					log.Warnf("Error creating listening port for KNX on address %s", ipv4Addr.String())
+					continue
+				}
+
+				go func() {
+					buf := make([]byte, 1024)
+
+					////////////////////////////////////////////////////////////////////////////////////////////////////
+					// Send the Search Request using the current network device
+					////////////////////////////////////////////////////////////////////////////////////////////////////
+
+					localIp := udpSocket.LocalAddr().(*net.UDPAddr).IP
+					localPort := udpSocket.LocalAddr().(*net.UDPAddr).Port
+
+					// Prepare the discovery packet data
+					searchRequestMessage := model.NewSearchRequest(model.NewHPAIDiscoveryEndpoint(
+						model.HostProtocolCode_IPV4_UDP,
+						model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
+						uint16(localPort)))
+					writeBuffer := utils.NewWriteBuffer()
+					err := searchRequestMessage.Serialize(*writeBuffer)
+					if err != nil {
+						panic("Failed preparing search request.")
+					}
+
+					// This is the multicast address and port KNX devices are supposed to listen to.
+					destination := &net.UDPAddr{IP: net.IPv4(224, 0, 23, 12), Port: 3671}
+
+					// Send the message
+					_, err = udpSocket.WriteTo(writeBuffer.GetBytes(), destination)
+					if err != nil {
+						panic("Failed sending search request.")
+					}
+
+					////////////////////////////////////////////////////////////////////////////////////////////////////
+					// Wait for a response from a device that supports tunneling
+					////////////////////////////////////////////////////////////////////////////////////////////////////
+
+					var searchResponse *model.SearchResponse
+					var gatewayAddr *net.UDPAddr
+					for searchResponse == nil {
+						// Read a new packet from the socket
+						_, src, err := udpSocket.ReadFromUDP(buf)
+						if err != nil {
+							panic("Error reading from KNX UDP socket")
+						}
+
+						readBuffer := utils.NewReadBuffer(buf)
+						knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
+						if err != nil {
+							hexEncodedPayload := hex.EncodeToString(buf)
+							panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
+						}
+
+						switch knxMessage.Child.(type) {
+						// If this is a search response and the current device supports tunneling,
+						// we've found what we're looking for.
+						case *model.SearchResponse:
+							curSearchResponse := model.CastSearchResponse(knxMessage)
+							for _, serviceId := range curSearchResponse.DibSuppSvcFamilies.ServiceIds {
+								switch (*serviceId).Child.(type) {
+								case *model.KnxNetIpTunneling:
+									searchResponse = curSearchResponse
+									gatewayAddr = src
+									break
+								}
+							}
+						}
+
+						////////////////////////////////////////////////////////////////////////////////////////////////////
+						// Send a connection request to the device we just found
+						////////////////////////////////////////////////////////////////////////////////////////////////////
+
+						connectionRequestMessage := model.NewConnectionRequest(
+							model.NewHPAIDiscoveryEndpoint(
+								model.HostProtocolCode_IPV4_UDP,
+								model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
+								uint16(localPort)),
+							model.NewHPAIDataEndpoint(
+								model.HostProtocolCode_IPV4_UDP,
+								model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
+								uint16(localPort)),
+							model.NewConnectionRequestInformationTunnelConnection(model.KnxLayer_TUNNEL_LINK_LAYER))
+						writeBuffer = utils.NewWriteBuffer()
+						err = connectionRequestMessage.Serialize(*writeBuffer)
+						if err != nil {
+							panic("Failed preparing connection request.")
+						}
+
+						// Send the message
+						_, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
+
+						////////////////////////////////////////////////////////////////////////////////////////////////////
+						// Wait for a connection response
+						////////////////////////////////////////////////////////////////////////////////////////////////////
+
+						var communicationChannelId uint8
+						for communicationChannelId == 0 {
+							// Read a new packet from the socket
+							_, src, err := udpSocket.ReadFromUDP(buf)
+							if err != nil {
+								panic("Error reading from KNX UDP socket")
+							}
+
+							readBuffer := utils.NewReadBuffer(buf)
+							knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
+							if err != nil {
+								hexEncodedPayload := hex.EncodeToString(buf)
+								panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
+							}
+
+							switch knxMessage.Child.(type) {
+							case *model.ConnectionResponse:
+								connectionResponse := model.CastConnectionResponse(knxMessage)
+								if connectionResponse.Status == model.Status_NO_ERROR {
+									communicationChannelId = connectionResponse.CommunicationChannelId
+								} else {
+									panic("Got an error while connecting")
+								}
+							}
+
+							////////////////////////////////////////////////////////////////////////////////////////////////////
+							// Send a config connection request
+							////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							/*configConnectionRequestMessage := model.NewConnectionRequest(
+							      model.NewHPAIDiscoveryEndpoint(
+							          model.HostProtocolCode_IPV4_UDP,
+							          model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
+							          uint16(localPort)),
+							      model.NewHPAIDataEndpoint(
+							          model.HostProtocolCode_IPV4_UDP,
+							          model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
+							          uint16(localPort)),
+							      model.NewConnectionRequestInformationDeviceManagement())
+							  writeBuffer = utils.NewWriteBuffer()
+							  err = configConnectionRequestMessage.Serialize(*writeBuffer)
+							  if err != nil {
+							      panic("Failed preparing connection request.")
+							  }
+
+							  // Send the message
+							  _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
+
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+							  // Wait for a config connection response
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							  var configCommunicationChannelId uint8
+							  for configCommunicationChannelId == 0 {
+							      // Read a new packet from the socket
+							      _, src, err := udpSocket.ReadFromUDP(buf)
+							      if err != nil {
+							          panic("Error reading from KNX UDP socket")
+							      }
+
+							      readBuffer := utils.NewReadBuffer(buf)
+							      knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
+							      if err != nil {
+							          hexEncodedPayload := hex.EncodeToString(buf)
+							          panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
+							      }
+
+							      switch knxMessage.Child.(type) {
+							      case *model.ConnectionResponse:
+							          connectionResponse := model.CastConnectionResponse(knxMessage)
+							          if connectionResponse.Status == model.Status_NO_ERROR {
+							              configCommunicationChannelId = connectionResponse.CommunicationChannelId
+							          } else {
+							              panic("Got an error while connecting")
+							          }
+
+							      // Just ACK any incoming tunneling requests
+							      case *model.TunnelingRequest:
+							          tunnelingRequest := model.CastTunnelingRequest(knxMessage)
+
+							          tunnelingResponse := model.NewTunnelingResponse(
+							              model.NewTunnelingResponseDataBlock(
+							                  tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
+							                  tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
+							                  model.Status_NO_ERROR))
+							          writeBuffer := utils.NewWriteBuffer()
+							          err = tunnelingResponse.Serialize(*writeBuffer)
+							          if err != nil {
+							              panic("Failed preparing tunneling response.")
+							          }
+
+							          // Send the message
+							          _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
+							          if err != nil {
+							              panic("Failed sending tunneling response.")
+							          }
+							      }
+							  }
+
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+							  // Send a prop-read request for PID_MEDIUM_TYPE
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							  mediumTypePropRequestMessage := model.NewDeviceConfigurationRequest(
+							      model.NewDeviceConfigurationRequestDataBlock(configCommunicationChannelId, uint8(0)),
+							      model.NewMPropReadReq(uint16(8), uint8(1), uint8(51), uint8(1), uint16(1)))
+							  writeBuffer = utils.NewWriteBuffer()
+							  err = mediumTypePropRequestMessage.Serialize(*writeBuffer)
+							  if err != nil {
+							      panic("Failed preparing config disconnect request.")
+							  }
+
+							  // Send the message
+							  _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
+
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+							  // Wait for a prop read response
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							  var mediumType uint16
+							  for mediumType == 0 {
+							      // Read a new packet from the socket
+							      _, src, err := udpSocket.ReadFromUDP(buf)
+							      if err != nil {
+							          panic("Error reading from KNX UDP socket")
+							      }
+
+							      readBuffer := utils.NewReadBuffer(buf)
+							      knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
+							      if err != nil {
+							          hexEncodedPayload := hex.EncodeToString(buf)
+							          panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
+							      }
+
+							      switch knxMessage.Child.(type) {
+							      case *model.DeviceConfigurationRequest:
+							          deviceConfigurationRequest := model.CastDeviceConfigurationRequest(knxMessage)
+							          switch deviceConfigurationRequest.Cemi.Child.(type) {
+							          case *model.MPropReadCon:
+							              readCon := model.CastMPropReadCon(deviceConfigurationRequest.Cemi)
+							              // TODO: This should be renamed to "Data"
+							              mediumType = readCon.Unknown
+
+							              // Send and ACK for this response
+							              tunnelingResponse := model.NewTunnelingResponse(
+							                  model.NewTunnelingResponseDataBlock(
+							                      deviceConfigurationRequest.DeviceConfigurationRequestDataBlock.CommunicationChannelId,
+							                      deviceConfigurationRequest.DeviceConfigurationRequestDataBlock.SequenceCounter,
+							                      model.Status_NO_ERROR))
+							              writeBuffer := utils.NewWriteBuffer()
+							              err = tunnelingResponse.Serialize(*writeBuffer)
+							              if err != nil {
+							                  panic("Failed preparing tunneling response.")
+							              }
+
+							              // Send the message
+							              _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
+							              if err != nil {
+							                  panic("Failed sending tunneling response.")
+							              }
+							          }
+
+							      // Just ACK any incoming tunneling requests
+							      case *model.TunnelingRequest:
+							          tunnelingRequest := model.CastTunnelingRequest(knxMessage)
+
+							          tunnelingResponse := model.NewTunnelingResponse(
+							              model.NewTunnelingResponseDataBlock(
+							                  tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
+							                  tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
+							                  model.Status_NO_ERROR))
+							          writeBuffer := utils.NewWriteBuffer()
+							          err = tunnelingResponse.Serialize(*writeBuffer)
+							          if err != nil {
+							              panic("Failed preparing tunneling response.")
+							          }
+
+							          // Send the message
+							          _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
+							          if err != nil {
+							              panic("Failed sending tunneling response.")
+							          }
+							      }
+							  }
+
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+							  // Send a prop-read request for PID_MAX_APDULENGTH
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							  maxApdulengthPropRequestMessage := model.NewDeviceConfigurationRequest(
+							      model.NewDeviceConfigurationRequestDataBlock(configCommunicationChannelId, uint8(1)),
+							      model.NewMPropReadReq(uint16(0), uint8(1), uint8(56), uint8(1), uint16(1)))
+							  writeBuffer = utils.NewWriteBuffer()
+							  err = maxApdulengthPropRequestMessage.Serialize(*writeBuffer)
+							  if err != nil {
+							      panic("Failed preparing config disconnect request.")
+							  }
+
+							  // Send the message
+							  _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
+
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+							  // Wait for a prop read response
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							  var maxApdulength uint16
+							  for maxApdulength == 0 {
+							      // Read a new packet from the socket
+							      _, src, err := udpSocket.ReadFromUDP(buf)
+							      if err != nil {
+							          panic("Error reading from KNX UDP socket")
+							      }
+
+							      readBuffer := utils.NewReadBuffer(buf)
+							      knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
+							      if err != nil {
+							          hexEncodedPayload := hex.EncodeToString(buf)
+							          panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
+							      }
+
+							      switch knxMessage.Child.(type) {
+							      case *model.DeviceConfigurationRequest:
+							          deviceConfigurationRequest := model.CastDeviceConfigurationRequest(knxMessage)
+							          switch deviceConfigurationRequest.Cemi.Child.(type) {
+							          case *model.MPropReadCon:
+							              readCon := model.CastMPropReadCon(deviceConfigurationRequest.Cemi)
+							              // TODO: This should be renamed to "Data"
+							              maxApdulength = readCon.Unknown
+
+							              // Send and ACK for this response
+							              tunnelingResponse := model.NewTunnelingResponse(
+							                  model.NewTunnelingResponseDataBlock(
+							                      deviceConfigurationRequest.DeviceConfigurationRequestDataBlock.CommunicationChannelId,
+							                      deviceConfigurationRequest.DeviceConfigurationRequestDataBlock.SequenceCounter,
+							                      model.Status_NO_ERROR))
+							              writeBuffer := utils.NewWriteBuffer()
+							              err = tunnelingResponse.Serialize(*writeBuffer)
+							              if err != nil {
+							                  panic("Failed preparing tunneling response.")
+							              }
+
+							              // Send the message
+							              _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
+							              if err != nil {
+							                  panic("Failed sending tunneling response.")
+							              }
+							          }
+
+							      // Just ACK any incoming tunneling requests
+							      case *model.TunnelingRequest:
+							          tunnelingRequest := model.CastTunnelingRequest(knxMessage)
+
+							          tunnelingResponse := model.NewTunnelingResponse(
+							              model.NewTunnelingResponseDataBlock(
+							                  tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
+							                  tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
+							                  model.Status_NO_ERROR))
+							          writeBuffer := utils.NewWriteBuffer()
+							          err = tunnelingResponse.Serialize(*writeBuffer)
+							          if err != nil {
+							              panic("Failed preparing tunneling response.")
+							          }
+
+							          // Send the message
+							          _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
+							          if err != nil {
+							              panic("Failed sending tunneling response.")
+							          }
+							      }
+							  }
+
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+							  // Send a config connection disconnect request
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							  configConnectionDisconnectRequestMessage := model.NewDisconnectRequest(
+							      configCommunicationChannelId,
+							      model.NewHPAIControlEndpoint(
+							          model.HostProtocolCode_IPV4_UDP,
+							          model.NewIPAddress(utils.ByteArrayToInt8Array(localIp.To4())),
+							          uint16(localPort)))
+							  writeBuffer = utils.NewWriteBuffer()
+							  err = configConnectionDisconnectRequestMessage.Serialize(*writeBuffer)
+							  if err != nil {
+							      panic("Failed preparing config disconnect request.")
+							  }
+
+							  // Send the message
+							  _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
+
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+							  // Wait for a config disconnect response
+							  ////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							  configDisconnected := false
+							  for !configDisconnected {
+							      // Read a new packet from the socket
+							      _, src, err := udpSocket.ReadFromUDP(buf)
+							      if err != nil {
+							          panic("Error reading from KNX UDP socket")
+							      }
+
+							      readBuffer := utils.NewReadBuffer(buf)
+							      knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
+							      if err != nil {
+							          hexEncodedPayload := hex.EncodeToString(buf)
+							          panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
+							      }
+
+							      switch knxMessage.Child.(type) {
+							      case *model.DisconnectResponse:
+							          disconnectResponse := model.CastDisconnectResponse(knxMessage)
+							          if disconnectResponse.Status == model.Status_NO_ERROR {
+							              configDisconnected = true
+							          } else {
+							              panic("Got an error while connecting")
+							          }
+
+							      // Just ACK any incoming tunneling requests
+							      case *model.TunnelingRequest:
+							          tunnelingRequest := model.CastTunnelingRequest(knxMessage)
+
+							          tunnelingResponse := model.NewTunnelingResponse(
+							              model.NewTunnelingResponseDataBlock(
+							                  tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
+							                  tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
+							                  model.Status_NO_ERROR))
+							          writeBuffer := utils.NewWriteBuffer()
+							          err = tunnelingResponse.Serialize(*writeBuffer)
+							          if err != nil {
+							              panic("Failed preparing tunneling response.")
+							          }
+
+							          // Send the message
+							          _, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
+							          if err != nil {
+							              panic("Failed sending tunneling response.")
+							          }
+							      }
+							  }*/
+
+							////////////////////////////////////////////////////////////////////////////////////////////////////
+							// Send a device connection request to KNX address 1.1.10
+							////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							targetKnxAddress, err := ParseKnxAddressString("1.1.10")
+							if err != nil {
+								panic("Failed preparing discovery request.")
+							}
+							sourceAddress := &model.KnxAddress{
+								MainGroup:   0,
+								MiddleGroup: 0,
+								SubGroup:    0,
+							}
+							controlType := model.ControlType_CONNECT
+							deviceConnectionRequest := model.NewTunnelingRequest(
+								model.NewTunnelingRequestDataBlock(communicationChannelId, 0),
+								model.NewLDataReq(0, nil,
+									model.NewLDataFrameDataExt(false, 6, uint8(0),
+										sourceAddress, targetKnxAddress, uint8(0), true, false,
+										uint8(0), &controlType, nil, nil, nil, nil,
+										false, model.CEMIPriority_SYSTEM, false, false)))
+							writeBuffer = utils.NewWriteBuffer()
+							err = deviceConnectionRequest.Serialize(*writeBuffer)
+							if err != nil {
+								panic("Failed preparing device connection request.")
+							}
+
+							// Send the message
+							_, err = udpSocket.WriteTo(writeBuffer.GetBytes(), gatewayAddr)
+							if err != nil {
+								panic("Failed sending device connection request.")
+							}
+
+							////////////////////////////////////////////////////////////////////////////////////////////////////
+							// Wait for a device connection response
+							////////////////////////////////////////////////////////////////////////////////////////////////////
+
+							for {
+								// Read a new packet from the socket
+								_, src, err := udpSocket.ReadFromUDP(buf)
+								if err != nil {
+									panic("Error reading from KNX UDP socket")
+								}
+
+								readBuffer := utils.NewReadBuffer(buf)
+								knxMessage, err := model.KnxNetIpMessageParse(readBuffer)
+								if err != nil {
+									hexEncodedPayload := hex.EncodeToString(buf)
+									panic(fmt.Sprintf("Error decoding incoming KNX message from %v with payload %s", src, hexEncodedPayload))
+								}
+
+								switch knxMessage.Child.(type) {
+								case *model.TunnelingRequest:
+									tunnelingRequest := model.CastTunnelingRequest(knxMessage)
+
+									tunnelingResponse := model.NewTunnelingResponse(
+										model.NewTunnelingResponseDataBlock(
+											tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
+											tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
+											model.Status_NO_ERROR))
+									writeBuffer := utils.NewWriteBuffer()
+									err = tunnelingResponse.Serialize(*writeBuffer)
+									if err != nil {
+										panic("Failed preparing tunneling response.")
+									}
+
+									// Send the message
+									_, err = udpSocket.WriteTo(writeBuffer.GetBytes(), src)
+									if err != nil {
+										panic("Failed sending tunneling response.")
+									}
+								}
+							}
+						}
+					}
+				}()
+			}
+		}
+	}
 }
 
 func ParseKnxAddressString(knxAddressString string) ([]int8, error) {
-    if strings.Count(knxAddressString, ".") != 2 {
-        return nil, errors.New("this is not a valid knx address")
-    }
-
-    split := strings.Split(knxAddressString, ".")
-
-    mainSegment, err := strconv.Atoi(split[0])
-    if err != nil {
-        return nil, errors.New("this is not a valid knx address")
-    }
-    if mainSegment < 0 || mainSegment > 15 {
-        return nil, errors.New("this is not a valid knx address")
-    }
-
-    middleSegment, err := strconv.Atoi(split[1])
-    if err != nil {
-        return nil, errors.New("this is not a valid knx address")
-    }
-    if middleSegment < 0 || middleSegment > 15 {
-        return nil, errors.New("this is not a valid knx address")
-    }
-
-    subSegment, err := strconv.Atoi(split[2])
-    if err != nil {
-        return nil, errors.New("this is not a valid knx address")
-    }
-    if subSegment < 0 || subSegment > 255 {
-        return nil, errors.New("this is not a valid knx address")
-    }
-
-    return []int8{
-        int8(mainSegment << 4 | middleSegment),
-        int8(subSegment),
-    }, nil
+	if strings.Count(knxAddressString, ".") != 2 {
+		return nil, errors.New("this is not a valid knx address")
+	}
+
+	split := strings.Split(knxAddressString, ".")
+
+	mainSegment, err := strconv.Atoi(split[0])
+	if err != nil {
+		return nil, errors.New("this is not a valid knx address")
+	}
+	if mainSegment < 0 || mainSegment > 15 {
+		return nil, errors.New("this is not a valid knx address")
+	}
+
+	middleSegment, err := strconv.Atoi(split[1])
+	if err != nil {
+		return nil, errors.New("this is not a valid knx address")
+	}
+	if middleSegment < 0 || middleSegment > 15 {
+		return nil, errors.New("this is not a valid knx address")
+	}
+
+	subSegment, err := strconv.Atoi(split[2])
+	if err != nil {
+		return nil, errors.New("this is not a valid knx address")
+	}
+	if subSegment < 0 || subSegment > 255 {
+		return nil, errors.New("this is not a valid knx address")
+	}
+
+	return []int8{
+		int8(mainSegment<<4 | middleSegment),
+		int8(subSegment),
+	}, nil
 }
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go
index 6bd155a..cf22022 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpBrowser.go
@@ -19,7 +19,6 @@
 package knxnetip
 
 import (
-	"errors"
 	"fmt"
 	driverModel "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
 	"github.com/apache/plc4x/plc4go/internal/plc4go/spi"
@@ -123,50 +122,29 @@ func (b KnxNetIpBrowser) Browse(browseRequest apiModel.PlcBrowseRequest) <-chan
 						deviceConnectionRequest,
 						// The Gateway is now supposed to send an Ack to this request.
 						func(message interface{}) bool {
-							tunnelingResponse := driverModel.CastTunnelingResponse(message)
-							return tunnelingResponse != nil &&
-								tunnelingResponse.TunnelingResponseDataBlock.CommunicationChannelId == b.connection.CommunicationChannelId &&
-								tunnelingResponse.TunnelingResponseDataBlock.SequenceCounter == uint8(b.connection.SequenceCounter)
+							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 {
-							tunnelingResponse := driverModel.CastTunnelingResponse(message)
-							// As soon as we got a positive ACK, we expect the gateway to send a
-							// LDataCon message with the result of the connection request.
-							if tunnelingResponse.TunnelingResponseDataBlock.Status != driverModel.Status_NO_ERROR {
-								return errors.New("got a failure to process the connection request")
+							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)
 							}
-							return b.messageCodec.Expect(
-								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)
-									}
-									// In all cases send an Ack for the incoming message
-									ack := driverModel.NewTunnelingResponse(driverModel.NewTunnelingResponseDataBlock(
-										tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
-										tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
-										driverModel.Status_NO_ERROR))
-									done <- true
-									return b.messageCodec.Send(ack)
-								},
-								time.Second*1)
+							done <- true
+							return nil
 						},
 						time.Second*1)
 					select {
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go
index f10ec3a..2439065 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go
@@ -115,6 +115,7 @@ type KnxNetIpConnection struct {
 }
 
 type InternalKnxNetIpConnection interface {
+	Send(request *driverModel.KnxNetIpMessage) error
 	SendRequest(request *driverModel.KnxNetIpMessage, expect func(response interface{}) (bool, bool)) (int32, chan interface{})
 }
 
@@ -257,8 +258,6 @@ func (m *KnxNetIpConnection) Connect() <-chan plc4go.PlcConnectionConnectResult
 											lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
 											if lDataInd != nil {
 												m.handleIncomingTunnelingRequest(tunnelingRequest)
-											} else {
-												fmt.Printf("Not a LDataInd message %v\n", tunnelingRequest.Cemi)
 											}
 										}
 									}
@@ -496,6 +495,16 @@ func (m *KnxNetIpConnection) GetPlcValueHandler() spi.PlcValueHandler {
 	return m.valueHandler
 }
 
+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 {
+		tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId = m.CommunicationChannelId
+		tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter = m.getNewSequenceCounter()
+	}
+	return m.messageCodec.Send(request)
+}
+
 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)
@@ -527,15 +536,6 @@ func (m *KnxNetIpConnection) castIpToKnxAddress(ip net.IP) *driverModel.IPAddres
 }
 
 func (m *KnxNetIpConnection) handleIncomingTunnelingRequest(tunnelingRequest *driverModel.TunnelingRequest) {
-	// Send an Ack response for this message
-	tunnelingResponse := driverModel.NewTunnelingResponse(driverModel.NewTunnelingResponseDataBlock(
-		m.CommunicationChannelId, tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
-		driverModel.Status_NO_ERROR))
-	err := m.messageCodec.Send(tunnelingResponse)
-	if err != nil {
-		return
-	}
-
 	go func() {
 		lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi.Child)
 		if lDataInd != nil {
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpField.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpField.go
index c543ba2..361dad4 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpField.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpField.go
@@ -19,335 +19,335 @@
 package knxnetip
 
 import (
-    "errors"
-    driverModel "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
-    apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
-    "strconv"
-    "strings"
+	"errors"
+	driverModel "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
+	apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+	"strconv"
+	"strings"
 )
 
 type KnxNetIpField interface {
-    IsPatternField() bool
-    matches(knxGroupAddress *driverModel.KnxGroupAddress) bool
-    toGroupAddress() *driverModel.KnxGroupAddress
-    apiModel.PlcField
+	IsPatternField() bool
+	matches(knxGroupAddress *driverModel.KnxGroupAddress) bool
+	toGroupAddress() *driverModel.KnxGroupAddress
+	apiModel.PlcField
 }
 
 type KnxNetIpGroupAddress3LevelPlcField struct {
-    FieldType *driverModel.KnxDatapointType
-    // 5 Bits: Values 0-31
-    MainGroup string
-    // 3 Bits: values 0-7
-    MiddleGroup string
-    // 8 Bits
-    SubGroup string
-    KnxNetIpField
+	FieldType *driverModel.KnxDatapointType
+	// 5 Bits: Values 0-31
+	MainGroup string
+	// 3 Bits: values 0-7
+	MiddleGroup string
+	// 8 Bits
+	SubGroup string
+	KnxNetIpField
 }
 
 func NewKnxNetIpGroupAddress3LevelPlcField(fieldType *driverModel.KnxDatapointType, mainGroup string, middleGroup string, subGroup string) KnxNetIpGroupAddress3LevelPlcField {
-    return KnxNetIpGroupAddress3LevelPlcField{
-        FieldType:   fieldType,
-        MainGroup:   mainGroup,
-        MiddleGroup: middleGroup,
-        SubGroup:    subGroup,
-    }
+	return KnxNetIpGroupAddress3LevelPlcField{
+		FieldType:   fieldType,
+		MainGroup:   mainGroup,
+		MiddleGroup: middleGroup,
+		SubGroup:    subGroup,
+	}
 }
 
 func (k KnxNetIpGroupAddress3LevelPlcField) GetTypeName() string {
-    return k.FieldType.FormatName()
+	return k.FieldType.FormatName()
 }
 
 func (k KnxNetIpGroupAddress3LevelPlcField) GetQuantity() uint16 {
-    return 1
+	return 1
 }
 
 func (k KnxNetIpGroupAddress3LevelPlcField) IsPatternField() bool {
-    _, err := strconv.Atoi(k.MainGroup)
-    if err == nil {
-        _, err = strconv.Atoi(k.MiddleGroup)
-        if err == nil {
-            _, err = strconv.Atoi(k.SubGroup)
-            if err == nil {
-                return false
-            }
-        }
-    }
-    return true
+	_, err := strconv.Atoi(k.MainGroup)
+	if err == nil {
+		_, err = strconv.Atoi(k.MiddleGroup)
+		if err == nil {
+			_, err = strconv.Atoi(k.SubGroup)
+			if err == nil {
+				return false
+			}
+		}
+	}
+	return true
 }
 
 func (k KnxNetIpGroupAddress3LevelPlcField) matches(knxGroupAddress *driverModel.KnxGroupAddress) bool {
-    level3KnxGroupAddress := driverModel.CastKnxGroupAddress3Level(knxGroupAddress)
-    if level3KnxGroupAddress == nil {
-        return false
-    }
-    return matches(k.MainGroup, strconv.Itoa(int(level3KnxGroupAddress.MainGroup))) &&
-        matches(k.MiddleGroup, strconv.Itoa(int(level3KnxGroupAddress.MiddleGroup))) &&
-        matches(k.SubGroup, strconv.Itoa(int(level3KnxGroupAddress.SubGroup)))
+	level3KnxGroupAddress := driverModel.CastKnxGroupAddress3Level(knxGroupAddress)
+	if level3KnxGroupAddress == nil {
+		return false
+	}
+	return matches(k.MainGroup, strconv.Itoa(int(level3KnxGroupAddress.MainGroup))) &&
+		matches(k.MiddleGroup, strconv.Itoa(int(level3KnxGroupAddress.MiddleGroup))) &&
+		matches(k.SubGroup, strconv.Itoa(int(level3KnxGroupAddress.SubGroup)))
 }
 
 func (k KnxNetIpGroupAddress3LevelPlcField) toGroupAddress() *driverModel.KnxGroupAddress {
-    mainGroup, err := strconv.Atoi(k.MainGroup)
-    if err != nil {
-        return nil
-    }
-    middleGroup, err := strconv.Atoi(k.MiddleGroup)
-    if err != nil {
-        return nil
-    }
-    subGroup, err := strconv.Atoi(k.SubGroup)
-    if err != nil {
-        return nil
-    }
-    ga := &driverModel.KnxGroupAddress{}
-    l3 := &driverModel.KnxGroupAddress3Level{
-        MainGroup:   uint8(mainGroup),
-        MiddleGroup: uint8(middleGroup),
-        SubGroup:    uint8(subGroup),
-        Parent:      ga,
-    }
-    ga.Child = l3
-    return ga
+	mainGroup, err := strconv.Atoi(k.MainGroup)
+	if err != nil {
+		return nil
+	}
+	middleGroup, err := strconv.Atoi(k.MiddleGroup)
+	if err != nil {
+		return nil
+	}
+	subGroup, err := strconv.Atoi(k.SubGroup)
+	if err != nil {
+		return nil
+	}
+	ga := &driverModel.KnxGroupAddress{}
+	l3 := &driverModel.KnxGroupAddress3Level{
+		MainGroup:   uint8(mainGroup),
+		MiddleGroup: uint8(middleGroup),
+		SubGroup:    uint8(subGroup),
+		Parent:      ga,
+	}
+	ga.Child = l3
+	return ga
 }
 
 type KnxNetIpGroupAddress2LevelPlcField struct {
-    FieldType *driverModel.KnxDatapointType
-    // 5 Bits: Values 0-31
-    MainGroup string
-    // 11 Bits
-    SubGroup string
-    KnxNetIpField
+	FieldType *driverModel.KnxDatapointType
+	// 5 Bits: Values 0-31
+	MainGroup string
+	// 11 Bits
+	SubGroup string
+	KnxNetIpField
 }
 
 func NewKnxNetIpGroupAddress2LevelPlcField(fieldType *driverModel.KnxDatapointType, mainGroup string, subGroup string) KnxNetIpGroupAddress2LevelPlcField {
-    return KnxNetIpGroupAddress2LevelPlcField{
-        FieldType: fieldType,
-        MainGroup: mainGroup,
-        SubGroup:  subGroup,
-    }
+	return KnxNetIpGroupAddress2LevelPlcField{
+		FieldType: fieldType,
+		MainGroup: mainGroup,
+		SubGroup:  subGroup,
+	}
 }
 
 func (k KnxNetIpGroupAddress2LevelPlcField) GetTypeName() string {
-    return k.FieldType.FormatName()
+	return k.FieldType.FormatName()
 }
 
 func (k KnxNetIpGroupAddress2LevelPlcField) GetQuantity() uint16 {
-    return 1
+	return 1
 }
 
 func (k KnxNetIpGroupAddress2LevelPlcField) IsPatternField() bool {
-    _, err := strconv.Atoi(k.MainGroup)
-    if err == nil {
-        _, err = strconv.Atoi(k.SubGroup)
-        if err == nil {
-            return false
-        }
-    }
-    return true
+	_, err := strconv.Atoi(k.MainGroup)
+	if err == nil {
+		_, err = strconv.Atoi(k.SubGroup)
+		if err == nil {
+			return false
+		}
+	}
+	return true
 }
 
 func (k KnxNetIpGroupAddress2LevelPlcField) matches(knxGroupAddress *driverModel.KnxGroupAddress) bool {
-    level2KnxGroupAddress := driverModel.CastKnxGroupAddress2Level(knxGroupAddress)
-    if level2KnxGroupAddress == nil {
-        return false
-    }
-    return matches(k.MainGroup, strconv.Itoa(int(level2KnxGroupAddress.MainGroup))) &&
-        matches(k.SubGroup, strconv.Itoa(int(level2KnxGroupAddress.SubGroup)))
+	level2KnxGroupAddress := driverModel.CastKnxGroupAddress2Level(knxGroupAddress)
+	if level2KnxGroupAddress == nil {
+		return false
+	}
+	return matches(k.MainGroup, strconv.Itoa(int(level2KnxGroupAddress.MainGroup))) &&
+		matches(k.SubGroup, strconv.Itoa(int(level2KnxGroupAddress.SubGroup)))
 }
 
 func (k KnxNetIpGroupAddress2LevelPlcField) toGroupAddress() *driverModel.KnxGroupAddress {
-    mainGroup, err := strconv.Atoi(k.MainGroup)
-    if err != nil {
-        return nil
-    }
-    subGroup, err := strconv.Atoi(k.SubGroup)
-    if err != nil {
-        return nil
-    }
-    ga := &driverModel.KnxGroupAddress{}
-    l3 := &driverModel.KnxGroupAddress2Level{
-        MainGroup: uint8(mainGroup),
-        SubGroup:  uint16(subGroup),
-        Parent:    ga,
-    }
-    ga.Child = l3
-    return ga
+	mainGroup, err := strconv.Atoi(k.MainGroup)
+	if err != nil {
+		return nil
+	}
+	subGroup, err := strconv.Atoi(k.SubGroup)
+	if err != nil {
+		return nil
+	}
+	ga := &driverModel.KnxGroupAddress{}
+	l3 := &driverModel.KnxGroupAddress2Level{
+		MainGroup: uint8(mainGroup),
+		SubGroup:  uint16(subGroup),
+		Parent:    ga,
+	}
+	ga.Child = l3
+	return ga
 }
 
 type KnxNetIpGroupAddress1LevelPlcField struct {
-    FieldType *driverModel.KnxDatapointType
-    // 16 Bits
-    MainGroup string
-    KnxNetIpField
+	FieldType *driverModel.KnxDatapointType
+	// 16 Bits
+	MainGroup string
+	KnxNetIpField
 }
 
 func NewKnxNetIpGroupAddress1LevelPlcField(fieldType *driverModel.KnxDatapointType, mainGroup string) KnxNetIpGroupAddress1LevelPlcField {
-    return KnxNetIpGroupAddress1LevelPlcField{
-        FieldType: fieldType,
-        MainGroup: mainGroup,
-    }
+	return KnxNetIpGroupAddress1LevelPlcField{
+		FieldType: fieldType,
+		MainGroup: mainGroup,
+	}
 }
 
 func (k KnxNetIpGroupAddress1LevelPlcField) GetTypeName() string {
-    return k.FieldType.FormatName()
+	return k.FieldType.FormatName()
 }
 
 func (k KnxNetIpGroupAddress1LevelPlcField) GetQuantity() uint16 {
-    return 1
+	return 1
 }
 
 func (k KnxNetIpGroupAddress1LevelPlcField) IsPatternField() bool {
-    _, err := strconv.Atoi(k.MainGroup)
-    if err == nil {
-        return false
-    }
-    return true
+	_, err := strconv.Atoi(k.MainGroup)
+	if err == nil {
+		return false
+	}
+	return true
 }
 
 func (k KnxNetIpGroupAddress1LevelPlcField) matches(knxGroupAddress *driverModel.KnxGroupAddress) bool {
-    level1KnxGroupAddress := driverModel.CastKnxGroupAddressFreeLevel(knxGroupAddress)
-    if level1KnxGroupAddress == nil {
-        return false
-    }
-    return matches(k.MainGroup, strconv.Itoa(int(level1KnxGroupAddress.SubGroup)))
+	level1KnxGroupAddress := driverModel.CastKnxGroupAddressFreeLevel(knxGroupAddress)
+	if level1KnxGroupAddress == nil {
+		return false
+	}
+	return matches(k.MainGroup, strconv.Itoa(int(level1KnxGroupAddress.SubGroup)))
 }
 
 func (k KnxNetIpGroupAddress1LevelPlcField) toGroupAddress() *driverModel.KnxGroupAddress {
-    mainGroup, err := strconv.Atoi(k.MainGroup)
-    if err != nil {
-        return nil
-    }
-    ga := &driverModel.KnxGroupAddress{}
-    l3 := &driverModel.KnxGroupAddressFreeLevel{
-        SubGroup: uint16(mainGroup),
-        Parent:   ga,
-    }
-    ga.Child = l3
-    return ga
+	mainGroup, err := strconv.Atoi(k.MainGroup)
+	if err != nil {
+		return nil
+	}
+	ga := &driverModel.KnxGroupAddress{}
+	l3 := &driverModel.KnxGroupAddressFreeLevel{
+		SubGroup: uint16(mainGroup),
+		Parent:   ga,
+	}
+	ga.Child = l3
+	return ga
 }
 
 type KnxNetIpDevicePropertyAddressPlcField struct {
-    FieldType  *driverModel.KnxDatapointType
-    // 5 Bits: Values 0-31
-    MainGroup string
-    // 3 Bits: values 0-7
-    MiddleGroup string
-    // 8 Bits
-    SubGroup   string
-    ObjectId   string
-    PropertyId string
-    KnxNetIpField
+	FieldType *driverModel.KnxDatapointType
+	// 5 Bits: Values 0-31
+	MainGroup string
+	// 3 Bits: values 0-7
+	MiddleGroup string
+	// 8 Bits
+	SubGroup   string
+	ObjectId   string
+	PropertyId string
+	KnxNetIpField
 }
 
 func NewKnxNetIpDevicePropertyAddressPlcField(fieldType *driverModel.KnxDatapointType, mainGroup string, middleGroup string, subGroup string, objectId string, propertyId string) KnxNetIpDevicePropertyAddressPlcField {
-    return KnxNetIpDevicePropertyAddressPlcField{
-        FieldType:   fieldType,
-        MainGroup:   mainGroup,
-        MiddleGroup: middleGroup,
-        SubGroup:    subGroup,
-        ObjectId:    objectId,
-        PropertyId:  propertyId,
-    }
+	return KnxNetIpDevicePropertyAddressPlcField{
+		FieldType:   fieldType,
+		MainGroup:   mainGroup,
+		MiddleGroup: middleGroup,
+		SubGroup:    subGroup,
+		ObjectId:    objectId,
+		PropertyId:  propertyId,
+	}
 }
 
 func (k KnxNetIpDevicePropertyAddressPlcField) GetTypeName() string {
-    return k.FieldType.FormatName()
+	return k.FieldType.FormatName()
 }
 
 func (k KnxNetIpDevicePropertyAddressPlcField) GetQuantity() uint16 {
-    return 1
+	return 1
 }
 
 func (k KnxNetIpDevicePropertyAddressPlcField) IsPatternField() bool {
-    _, err := strconv.Atoi(k.MainGroup)
-    if err == nil {
-        _, err = strconv.Atoi(k.MiddleGroup)
-        if err == nil {
-            _, err = strconv.Atoi(k.SubGroup)
-            if err == nil {
-                return false
-            }
-        }
-    }
-    return true
+	_, err := strconv.Atoi(k.MainGroup)
+	if err == nil {
+		_, err = strconv.Atoi(k.MiddleGroup)
+		if err == nil {
+			_, err = strconv.Atoi(k.SubGroup)
+			if err == nil {
+				return false
+			}
+		}
+	}
+	return true
 }
 
 func (k KnxNetIpDevicePropertyAddressPlcField) matches(knxGroupAddress *driverModel.KnxGroupAddress) bool {
-    level3KnxGroupAddress := driverModel.CastKnxGroupAddress3Level(knxGroupAddress)
-    if level3KnxGroupAddress == nil {
-        return false
-    }
-    return matches(k.MainGroup, strconv.Itoa(int(level3KnxGroupAddress.MainGroup))) &&
-        matches(k.MiddleGroup, strconv.Itoa(int(level3KnxGroupAddress.MiddleGroup))) &&
-        matches(k.SubGroup, strconv.Itoa(int(level3KnxGroupAddress.SubGroup)))
+	level3KnxGroupAddress := driverModel.CastKnxGroupAddress3Level(knxGroupAddress)
+	if level3KnxGroupAddress == nil {
+		return false
+	}
+	return matches(k.MainGroup, strconv.Itoa(int(level3KnxGroupAddress.MainGroup))) &&
+		matches(k.MiddleGroup, strconv.Itoa(int(level3KnxGroupAddress.MiddleGroup))) &&
+		matches(k.SubGroup, strconv.Itoa(int(level3KnxGroupAddress.SubGroup)))
 }
 
 func (k KnxNetIpDevicePropertyAddressPlcField) toKnxAddress() *driverModel.KnxAddress {
-    mainGroup, err := strconv.Atoi(k.MainGroup)
-    if err != nil {
-        return nil
-    }
-    middleGroup, err := strconv.Atoi(k.MiddleGroup)
-    if err != nil {
-        return nil
-    }
-    subGroup, err := strconv.Atoi(k.SubGroup)
-    if err != nil {
-        return nil
-    }
-    ga := &driverModel.KnxAddress{
-        MainGroup:   uint8(mainGroup),
-        MiddleGroup: uint8(middleGroup),
-        SubGroup:    uint8(subGroup),
-    }
-    return ga
+	mainGroup, err := strconv.Atoi(k.MainGroup)
+	if err != nil {
+		return nil
+	}
+	middleGroup, err := strconv.Atoi(k.MiddleGroup)
+	if err != nil {
+		return nil
+	}
+	subGroup, err := strconv.Atoi(k.SubGroup)
+	if err != nil {
+		return nil
+	}
+	ga := &driverModel.KnxAddress{
+		MainGroup:   uint8(mainGroup),
+		MiddleGroup: uint8(middleGroup),
+		SubGroup:    uint8(subGroup),
+	}
+	return ga
 }
 
 func CastToKnxNetIpFieldFromPlcField(plcField apiModel.PlcField) (KnxNetIpField, error) {
-    if knxNetIpField, ok := plcField.(KnxNetIpField); ok {
-        return knxNetIpField, nil
-    }
-    return nil, errors.New("couldn't cast to KnxNetIpField")
+	if knxNetIpField, ok := plcField.(KnxNetIpField); ok {
+		return knxNetIpField, nil
+	}
+	return nil, errors.New("couldn't cast to KnxNetIpField")
 }
 
 func matches(pattern string, groupAddressPart string) bool {
-    // A "*" simply matches everything
-    if pattern == "*" {
-        return true
-    }
-    // If the pattern starts and ends with square brackets, it's a list of values or range queries
-    if strings.HasPrefix(pattern, "[") && strings.HasSuffix(pattern, "]") {
-        matches := false
-        for _, segment := range strings.Split(pattern, ",") {
-            if strings.Contains(segment, "-") {
-                // If the segment contains a "-", then it's a range query
-                split := strings.Split(segment, "-")
-                if len(split) == 2 {
-                    if val, err := strconv.Atoi(groupAddressPart); err != nil {
-                        var err error
-                        var from int
-                        if from, err = strconv.Atoi(split[0]); err != nil {
-                            continue
-                        }
-                        if val < from {
-                            continue
-                        }
-                        var to int
-                        if to, err = strconv.Atoi(split[1]); err == nil {
-                            continue
-                        }
-                        if val > to {
-                            continue
-                        }
-                        matches = true
-                    }
-                }
-            } else if segment == groupAddressPart {
-                // In all other cases it's an explicit value
-                matches = true
-            }
-        }
-        return matches
-    } else {
-        return pattern == groupAddressPart
-    }
+	// A "*" simply matches everything
+	if pattern == "*" {
+		return true
+	}
+	// If the pattern starts and ends with square brackets, it's a list of values or range queries
+	if strings.HasPrefix(pattern, "[") && strings.HasSuffix(pattern, "]") {
+		matches := false
+		for _, segment := range strings.Split(pattern, ",") {
+			if strings.Contains(segment, "-") {
+				// If the segment contains a "-", then it's a range query
+				split := strings.Split(segment, "-")
+				if len(split) == 2 {
+					if val, err := strconv.Atoi(groupAddressPart); err != nil {
+						var err error
+						var from int
+						if from, err = strconv.Atoi(split[0]); err != nil {
+							continue
+						}
+						if val < from {
+							continue
+						}
+						var to int
+						if to, err = strconv.Atoi(split[1]); err == nil {
+							continue
+						}
+						if val > to {
+							continue
+						}
+						matches = true
+					}
+				}
+			} else if segment == groupAddressPart {
+				// In all other cases it's an explicit value
+				matches = true
+			}
+		}
+		return matches
+	} else {
+		return pattern == groupAddressPart
+	}
 }
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpMessageCodec.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpMessageCodec.go
index 4a9db18..f756c9a 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpMessageCodec.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpMessageCodec.go
@@ -19,176 +19,196 @@
 package knxnetip
 
 import (
-    "errors"
-    "fmt"
-    "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/transports"
-    "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
-    "time"
+	"errors"
+	"fmt"
+	"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/transports"
+	"github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
+	"time"
 )
 
 type KnxNetIpExpectation struct {
-    expiration     time.Time
-    acceptsMessage spi.AcceptsMessage
-    handleMessage  spi.HandleMessage
+	expiration     time.Time
+	acceptsMessage spi.AcceptsMessage
+	handleMessage  spi.HandleMessage
 }
 
 type KnxNetIpMessageCodec struct {
-    sequenceCounter               int32
-    transportInstance             transports.TransportInstance
-    messageInterceptor            func(message interface{})
-    defaultIncomingMessageChannel chan interface{}
-    expectations                  []KnxNetIpExpectation
+	sequenceCounter               int32
+	transportInstance             transports.TransportInstance
+	messageInterceptor            func(message interface{})
+	defaultIncomingMessageChannel chan interface{}
+	expectations                  []KnxNetIpExpectation
 }
 
 func NewKnxNetIpMessageCodec(transportInstance transports.TransportInstance, messageInterceptor func(message interface{})) *KnxNetIpMessageCodec {
-    codec := &KnxNetIpMessageCodec{
-        sequenceCounter:               0,
-        transportInstance:             transportInstance,
-        messageInterceptor:            messageInterceptor,
-        defaultIncomingMessageChannel: make(chan interface{}),
-        expectations:                  []KnxNetIpExpectation{},
-    }
-    // Start a worker that handles processing of responses
-    go work(codec)
-    return codec
+	codec := &KnxNetIpMessageCodec{
+		sequenceCounter:               0,
+		transportInstance:             transportInstance,
+		messageInterceptor:            messageInterceptor,
+		defaultIncomingMessageChannel: make(chan interface{}),
+		expectations:                  []KnxNetIpExpectation{},
+	}
+	// Start a worker that handles processing of responses
+	go work(codec)
+	return codec
 }
 
 func (m *KnxNetIpMessageCodec) Connect() error {
-    // "connect" to the remote UDP server
-    return m.transportInstance.Connect()
+	// "connect" to the remote UDP server
+	return m.transportInstance.Connect()
 }
 
 func (m *KnxNetIpMessageCodec) Disconnect() error {
-    return m.transportInstance.Close()
+	return m.transportInstance.Close()
 }
 
 func (m *KnxNetIpMessageCodec) Send(message interface{}) error {
-    // Cast the message to the correct type of struct
-    knxMessage := model.CastKnxNetIpMessage(message)
-    // Serialize the request
-    wb := utils.NewWriteBuffer()
-    err := knxMessage.Serialize(*wb)
-    if err != nil {
-        return errors.New("error serializing request " + err.Error())
-    }
-
-    // Send it to the PLC
-    err = m.transportInstance.Write(wb.GetBytes())
-    if err != nil {
-        return errors.New("error sending request " + err.Error())
-    }
-
-    return nil
+	// Cast the message to the correct type of struct
+	knxMessage := model.CastKnxNetIpMessage(message)
+	// Serialize the request
+	wb := utils.NewWriteBuffer()
+	err := knxMessage.Serialize(*wb)
+	if err != nil {
+		return errors.New("error serializing request " + err.Error())
+	}
+
+	// Send it to the PLC
+	err = m.transportInstance.Write(wb.GetBytes())
+	if err != nil {
+		return errors.New("error sending request " + err.Error())
+	}
+
+	return nil
 }
 
 func (m *KnxNetIpMessageCodec) Expect(acceptsMessage spi.AcceptsMessage, handleMessage spi.HandleMessage, ttl time.Duration) error {
-    expectation := KnxNetIpExpectation{
-        expiration:     time.Now().Add(ttl),
-        acceptsMessage: acceptsMessage,
-        handleMessage:  handleMessage,
-    }
-    m.expectations = append(m.expectations, expectation)
-    return nil
+	expectation := KnxNetIpExpectation{
+		expiration:     time.Now().Add(ttl),
+		acceptsMessage: acceptsMessage,
+		handleMessage:  handleMessage,
+	}
+	m.expectations = append(m.expectations, expectation)
+	return nil
 }
 
 func (m *KnxNetIpMessageCodec) SendRequest(message interface{}, acceptsMessage spi.AcceptsMessage, handleMessage spi.HandleMessage, ttl time.Duration) error {
-    // Send the actual message
-    err := m.Send(message)
-    if err != nil {
-        return err
-    }
-    return m.Expect(acceptsMessage, handleMessage, ttl)
+	// Send the actual message
+	err := m.Send(message)
+	if err != nil {
+		return err
+	}
+	return m.Expect(acceptsMessage, handleMessage, ttl)
 }
 
 func (m *KnxNetIpMessageCodec) GetDefaultIncomingMessageChannel() chan interface{} {
-    return m.defaultIncomingMessageChannel
+	return m.defaultIncomingMessageChannel
 }
 
 func (m *KnxNetIpMessageCodec) receive() (interface{}, error) {
-    // We need at least 6 bytes in order to know how big the packet is in total
-    if num, err := m.transportInstance.GetNumReadableBytes(); (err == nil) && (num >= 6) {
-        data, err := m.transportInstance.PeekReadableBytes(6)
-        if err != nil {
-            fmt.Printf("Got error reading: %s\n", err.Error())
-            // TODO: Possibly clean up ...
-            return nil, nil
-        }
-        // Get the size of the entire packet
-        packetSize := (uint32(data[4]) << 8) + uint32(data[5])
-        if num >= packetSize {
-            data, err = m.transportInstance.Read(packetSize)
-            if err != nil {
-                fmt.Printf("Got error reading: %s\n", err.Error())
-                // TODO: Possibly clean up ...
-                return nil, nil
-            }
-            rb := utils.NewReadBuffer(data)
-            knxMessage, err := model.KnxNetIpMessageParse(rb)
-            if err != nil {
-                // TODO: Possibly clean up ...
-                return nil, nil
-            }
-            return knxMessage, nil
-        } else {
-            fmt.Printf("Not enough bytes. Got: %d Need: %d\n", num, packetSize)
-        }
-    } else if err != nil {
-        fmt.Printf("Got error reading: %s\n", err.Error())
-    } else {
-        fmt.Printf("Only %d bytes available\n", num)
-    }
-    return nil, nil
+	// We need at least 6 bytes in order to know how big the packet is in total
+	if num, err := m.transportInstance.GetNumReadableBytes(); (err == nil) && (num >= 6) {
+		data, err := m.transportInstance.PeekReadableBytes(6)
+		if err != nil {
+			fmt.Printf("Got error reading: %s\n", err.Error())
+			// TODO: Possibly clean up ...
+			return nil, nil
+		}
+		// Get the size of the entire packet
+		packetSize := (uint32(data[4]) << 8) + uint32(data[5])
+		if num >= packetSize {
+			data, err = m.transportInstance.Read(packetSize)
+			if err != nil {
+				fmt.Printf("Got error reading: %s\n", err.Error())
+				// TODO: Possibly clean up ...
+				return nil, nil
+			}
+			rb := utils.NewReadBuffer(data)
+			knxMessage, err := model.KnxNetIpMessageParse(rb)
+			if err != nil {
+				// TODO: Possibly clean up ...
+				return nil, nil
+			}
+			return knxMessage, nil
+		} else {
+			fmt.Printf("Not enough bytes. Got: %d Need: %d\n", num, packetSize)
+		}
+	} else if err != nil {
+		fmt.Printf("Got error reading: %s\n", err.Error())
+	} else {
+		fmt.Printf("Only %d bytes available\n", num)
+	}
+	return nil, nil
 }
 
 func work(m *KnxNetIpMessageCodec) {
-    // Start an endless loop
-    // TODO: Provide some means to terminate this ...
-    for {
-        message, err := m.receive()
-        if err != nil {
-            fmt.Printf("got an error reading from transport %s", err.Error())
-        } else if message != nil {
-            now := time.Now()
-            // Give a message interceptor a chance to intercept
-            if m.messageInterceptor != nil {
-                m.messageInterceptor(message)
-            }
-            // Go through all expectations
-            messageHandled := false
-            for index, expectation := range m.expectations {
-                // Check if this expectation has expired.
-                if now.After(expectation.expiration) {
-                    // Remove this expectation from the list.
-                    m.expectations = append(m.expectations[:index], m.expectations[index+1:]...)
-                    break
-                }
-
-                // Check if the current message matches the expectations
-                // If it does, let it handle the message.
-                if accepts := expectation.acceptsMessage(message); accepts {
-                    err = expectation.handleMessage(message)
-                    if err == nil {
-                        messageHandled = true
-                        // Remove this expectation from the list.
-                        m.expectations = append(m.expectations[:index], m.expectations[index+1:]...)
-                    }
-                    break
-                }
-            }
-
-            // If the message has not been handled and a default handler is provided, call this ...
-            if !messageHandled {
-                m.defaultIncomingMessageChannel <- message
-            }
-        } else {
-            // Sleep for 10ms
-            time.Sleep(10 * time.Millisecond)
-        }
-    }
+	// Start an endless loop
+	// TODO: Provide some means to terminate this ...
+	for {
+		message, err := m.receive()
+		if err != nil {
+			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
+			tunnelingRequest := model.CastTunnelingRequest(message)
+			if tunnelingRequest != nil {
+				response := model.NewTunnelingResponse(
+					model.NewTunnelingResponseDataBlock(
+						tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId,
+						tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter,
+						model.Status_NO_ERROR),
+				)
+				_ = m.Send(response)
+			}
+
+			// Otherwise handle it
+			now := time.Now()
+			// Give a message interceptor a chance to intercept
+			if m.messageInterceptor != nil {
+				m.messageInterceptor(message)
+			}
+			// Go through all expectations
+			messageHandled := false
+			for index, expectation := range m.expectations {
+				// Check if this expectation has expired.
+				if now.After(expectation.expiration) {
+					// Remove this expectation from the list.
+					m.expectations = append(m.expectations[:index], m.expectations[index+1:]...)
+					break
+				}
+
+				// Check if the current message matches the expectations
+				// If it does, let it handle the message.
+				if accepts := expectation.acceptsMessage(message); accepts {
+					err = expectation.handleMessage(message)
+					if err == nil {
+						messageHandled = true
+						// Remove this expectation from the list.
+						m.expectations = append(m.expectations[:index], m.expectations[index+1:]...)
+					}
+					break
+				}
+			}
+
+			// If the message has not been handled and a default handler is provided, call this ...
+			if !messageHandled {
+				m.defaultIncomingMessageChannel <- message
+			}
+		} else {
+			// Sleep for 10ms
+			time.Sleep(10 * time.Millisecond)
+		}
+	}
 }
 
 func (m KnxNetIpMessageCodec) GetTransportInstance() transports.TransportInstance {
-    return m.transportInstance
+	return m.transportInstance
 }
diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go
index a84aaf8..7164199 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpReader.go
@@ -19,13 +19,17 @@
 package knxnetip
 
 import (
-    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"
-    "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
-    internalValues "github.com/apache/plc4x/plc4go/internal/plc4go/spi/values"
-    apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
-    apiValues "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+	"errors"
+	"fmt"
+	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"
+	"github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
+	internalValues "github.com/apache/plc4x/plc4go/internal/plc4go/spi/values"
+	apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+	apiValues "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+	"strconv"
+	"time"
 )
 
 type KnxNetIpReader struct {
@@ -44,6 +48,11 @@ func (m KnxNetIpReader) Read(readRequest apiModel.PlcReadRequest) <-chan apiMode
 	go func() {
 		responseCodes := map[string]apiModel.PlcResponseCode{}
 		plcValues := map[string]apiValues.PlcValue{}
+
+		// Sort the fields in direct properties, which will have to be actively read from the devices
+		// and group-addresses which will be locally processed from the local cache.
+		directProperties := map[driverModel.KnxAddress]map[string]KnxNetIpDevicePropertyAddressPlcField{}
+		groupAddresses := map[string]KnxNetIpField{}
 		for _, fieldName := range readRequest.GetFieldNames() {
 			// Get the knx field
 			field, err := CastToKnxNetIpFieldFromPlcField(readRequest.GetField(fieldName))
@@ -53,116 +62,439 @@ func (m KnxNetIpReader) Read(readRequest apiModel.PlcReadRequest) <-chan apiMode
 				continue
 			}
 
-			// Pattern fields can match more than one value, therefore we have to handle things differently
-			if field.IsPatternField() {
-				// Depending on the type of field, get the uint16 ids of all values that match the current field
-				matchedAddresses := map[uint16]*driverModel.KnxGroupAddress{}
-				switch field.(type) {
-				case KnxNetIpGroupAddress3LevelPlcField:
-					for key, value := range m.connection.leve3AddressCache {
-						if field.matches(value.Parent) {
-							matchedAddresses[key] = value.Parent
-						}
-					}
-				case KnxNetIpGroupAddress2LevelPlcField:
-					for key, value := range m.connection.leve2AddressCache {
-						if field.matches(value.Parent) {
-							matchedAddresses[key] = value.Parent
-						}
-					}
-				case KnxNetIpGroupAddress1LevelPlcField:
-					for key, value := range m.connection.leve1AddressCache {
-						if field.matches(value.Parent) {
-							matchedAddresses[key] = value.Parent
-						}
-					}
+			switch field.(type) {
+			case KnxNetIpDevicePropertyAddressPlcField:
+				propertyField := field.(KnxNetIpDevicePropertyAddressPlcField)
+				knxAddress := FieldToKnxAddress(propertyField)
+				if knxAddress == nil {
+					continue
+				}
+				if _, ok := directProperties[*knxAddress]; !ok {
+					directProperties[*knxAddress] = map[string]KnxNetIpDevicePropertyAddressPlcField{}
 				}
+				directProperties[*knxAddress][fieldName] = propertyField
+			default:
+				groupAddresses[fieldName] = field
+			}
+		}
 
-				// If not a single match was found, we'll return a "not found" message
-				if len(matchedAddresses) == 0 {
-					responseCodes[fieldName] = apiModel.PlcResponseCode_NOT_FOUND
+		// 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
 					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++
+			}
+
+			// 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
+		for fieldName, field := range groupAddresses {
+			responseCode, plcValue := m.readGroupAddress(field)
+			responseCodes[fieldName] = responseCode
+			plcValues[fieldName] = plcValue
+		}
+
+		// Assemble the results
+		result := internalModel.NewDefaultPlcReadResponse(readRequest, responseCodes, plcValues)
+		resultChan <- apiModel.PlcReadRequestResult{
+			Request:  readRequest,
+			Response: result,
+			Err:      nil,
+		}
+	}()
+	return resultChan
+}
 
-				// Go through all of the values and create a plc-struct from them
-				// where the string version of the address becomes the property name
-				// and the property value is the corresponding value (Other wise it
-				// would be impossible to know which of the fields the pattern matched
-				// a given value belongs to)
-				values := map[string]apiValues.PlcValue{}
-				for numericAddress, address := range matchedAddresses {
-					// Get the raw data from the cache
-					m.connection.valueCacheMutex.RLock()
-					int8s, _ := m.connection.valueCache[numericAddress]
-					m.connection.valueCacheMutex.RUnlock()
-
-					// If we don't have any field-type information, add the raw data
-					if field.GetTypeName() == "" {
-						values[GroupAddressToString(address)] =
-							internalValues.NewPlcByteArray(utils.Int8ArrayToByteArray(int8s))
-					} else {
-						// Decode the data according to the fields type
-						rb := utils.NewReadBuffer(utils.Int8ArrayToUint8Array(int8s))
-						plcValue, err := driverModel.KnxDatapointParse(rb, field.GetTypeName())
-						// If any of the values doesn't decode correctly, we can't return any
-						if err != nil {
-							responseCodes[fieldName] = apiModel.PlcResponseCode_INVALID_DATA
-							plcValues[fieldName] = nil
-							continue
-						}
-						values[GroupAddressToString(address)] = plcValue
+func (m KnxNetIpReader) connectToDevice(targetAddress driverModel.KnxAddress) error {
+	connectionSuccess := make(chan bool)
+	controlType := driverModel.ControlType_CONNECT
+	deviceConnectionRequest := driverModel.NewTunnelingRequest(
+		driverModel.NewTunnelingRequestDataBlock(0, 0),
+		driverModel.NewLDataReq(0, nil,
+			driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+				driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
+				uint8(0), true, false, uint8(0), &controlType, nil, nil,
+				nil, nil, 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.
+			apciType := driverModel.APCI_DEVICE_DESCRIPTOR_READ_PDU
+			dataFirstByte := int8(0)
+			deviceDescriptorReadRequest := driverModel.NewTunnelingRequest(
+				driverModel.NewTunnelingRequestDataBlock(0, 0),
+				driverModel.NewLDataReq(0, nil,
+					driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+						driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
+						uint8(1), false, true, uint8(0), nil, &apciType, nil,
+						&dataFirstByte, nil, 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
+					}
+					if dataFrame.Apci == nil {
+						return false
+					}
+					return *dataFrame.Apci == driverModel.APCI_DEVICE_DESCRIPTOR_RESPONSE_PDU
+					// TODO: Do something with the request ...
+				},
+				func(message interface{}) error {
+					controlType = driverModel.ControlType_ACK
+					// 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),
+									uint8(0), true, true, uint8(0), &controlType, nil, nil,
+									nil, nil, 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 {
+	controlType := driverModel.ControlType_DISCONNECT
+	deviceConnectionRequest := driverModel.NewTunnelingRequest(
+		driverModel.NewTunnelingRequestDataBlock(0, 0),
+		driverModel.NewLDataReq(0, nil,
+			driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+				&sourceAddress, KnxAddressToInt8Array(targetAddress), uint8(0), true, false,
+				uint8(0), &controlType, nil, nil, nil, nil,
+				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 frameDataExt.Control == true && frameDataExt.ControlType == nil
+		},
+		func(message interface{}) error {
+			tunnelingRequest := driverModel.CastTunnelingRequest(message)
+			lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+			frameDataExt := 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
+}
+
+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)
+
+	apci := driverModel.APCI_OTHER_PDU
+	extendedApci := driverModel.ExtendedAPCI_PROPERTY_VALUE_READ_PDU
+	data := make([]int8, 4)
+	// Object Id
+	data[0] = int8(objectId)
+	// Property Id
+	data[1] = int8(propertyId)
+	// First 4 bits = count
+	data[2] = 16
+	// Index (including last 4 bits of previous byte)
+	data[3] = 1
+	request := driverModel.NewTunnelingRequest(
+		driverModel.NewTunnelingRequestDataBlock(0, 0),
+		driverModel.NewLDataReq(0, nil,
+			driverModel.NewLDataFrameDataExt(false, 6, 0,
+				driverModel.NewKnxAddress(0, 0, 0), destinationAddressData, 5,
+				false, true, counter, nil, &apci, &extendedApci,
+				nil, data, true, 3, false, false)))
+
+	result := make(chan apiValues.PlcValue)
+	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.Apci != nil {
+				if *dataFrameExt.Apci != driverModel.APCI_OTHER_PDU {
+					return false
+				}
+				if *dataFrameExt.ExtendedApci != driverModel.ExtendedAPCI_PROPERTY_VALUE_RESPONSE_PDU {
+					return false
+				}
+				if *dataFrameExt.SourceAddress != *destinationAddress {
+					return false
 				}
+				if *Int8ArrayToKnxAddress(dataFrameExt.DestinationAddress) != *m.connection.ClientKnxAddress {
+					return false
+				}
+				if dataFrameExt.DataLength < 5 {
+					return false
+				}
+				if *dataFrameExt.Apci == driverModel.APCI_OTHER_PDU &&
+					*dataFrameExt.ExtendedApci == driverModel.ExtendedAPCI_PROPERTY_VALUE_RESPONSE_PDU &&
+					*dataFrameExt.SourceAddress == *destinationAddress &&
+					*Int8ArrayToKnxAddress(dataFrameExt.DestinationAddress) == *m.connection.ClientKnxAddress &&
+					dataFrameExt.DataLength >= 5 {
+					readBuffer := utils.NewReadBuffer(utils.Int8ArrayToUint8Array(dataFrameExt.Data))
+					curObjectId, _ := readBuffer.ReadUint8(8)
+					curPropertyId, _ := readBuffer.ReadUint8(8)
+					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)
 
-				// Add it to the result
-				responseCodes[fieldName] = apiModel.PlcResponseCode_OK
-				plcValues[fieldName] = internalValues.NewPlcStruct(values)
-				continue
-			} else {
-				// If it's not a pattern field, we can access the cached value a lot simpler
+			readBuffer := utils.NewReadBuffer(utils.Int8ArrayToUint8Array(dataFrameExt.Data))
+			// Skip the object id and property id as we already checked them
+			_, _ = readBuffer.ReadUint8(8)
+			_, _ = readBuffer.ReadUint8(8)
 
-				// Serialize the field to an uint16
-				wb := utils.NewWriteBuffer()
-				err = field.toGroupAddress().Serialize(*wb)
-				if err != nil {
-					responseCodes[fieldName] = apiModel.PlcResponseCode_INVALID_ADDRESS
-					plcValues[fieldName] = nil
-					continue
+			count, _ := readBuffer.ReadUint8(4)
+			index, _ := readBuffer.ReadUint16(12)
+
+			// TODO: Depending on the object id and property id, parse the remaining data accordingly.
+			fmt.Printf("Got object-id %d property-id %d count %d index %d\n", objectId, propertyId, count, index)
+
+			// Send back an ACK
+			controlType := driverModel.ControlType_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,
+							uint8(0), true, true, dataFrameExt.Counter, &controlType, nil,
+							nil, nil, nil, true, driverModel.CEMIPriority_SYSTEM,
+							false, false))))
+
+			result <- internalValues.NewPlcBOOL(true)
+			return nil
+		},
+		time.Second*5)
+
+	select {
+	case value := <-result:
+		return apiModel.PlcResponseCode_OK, value
+		/*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() {
+		// Depending on the type of field, get the uint16 ids of all values that match the current field
+		matchedAddresses := map[uint16]*driverModel.KnxGroupAddress{}
+		switch field.(type) {
+		case KnxNetIpGroupAddress3LevelPlcField:
+			for key, value := range m.connection.leve3AddressCache {
+				if field.matches(value.Parent) {
+					matchedAddresses[key] = value.Parent
 				}
-				rawAddress := wb.GetBytes()
-				address := (uint16(rawAddress[0]) << 8) | uint16(rawAddress[1]&0xFF)
-
-				// Get the value form the cache
-				m.connection.valueCacheMutex.RLock()
-				int8s, ok := m.connection.valueCache[address]
-				m.connection.valueCacheMutex.RUnlock()
-				if !ok {
-					responseCodes[fieldName] = apiModel.PlcResponseCode_NOT_FOUND
-					plcValues[fieldName] = nil
-					continue
+			}
+		case KnxNetIpGroupAddress2LevelPlcField:
+			for key, value := range m.connection.leve2AddressCache {
+				if field.matches(value.Parent) {
+					matchedAddresses[key] = value.Parent
 				}
+			}
+		case KnxNetIpGroupAddress1LevelPlcField:
+			for key, value := range m.connection.leve1AddressCache {
+				if field.matches(value.Parent) {
+					matchedAddresses[key] = value.Parent
+				}
+			}
+		}
+
+		// If not a single match was found, we'll return a "not found" message
+		if len(matchedAddresses) == 0 {
+			return apiModel.PlcResponseCode_NOT_FOUND, nil
+		}
+
+		// Go through all of the values and create a plc-struct from them
+		// where the string version of the address becomes the property name
+		// and the property value is the corresponding value (Other wise it
+		// would be impossible to know which of the fields the pattern matched
+		// a given value belongs to)
+		values := map[string]apiValues.PlcValue{}
+		for numericAddress, address := range matchedAddresses {
+			// Get the raw data from the cache
+			m.connection.valueCacheMutex.RLock()
+			int8s, _ := m.connection.valueCache[numericAddress]
+			m.connection.valueCacheMutex.RUnlock()
 
+			// If we don't have any field-type information, add the raw data
+			if field.GetTypeName() == "" {
+				values[GroupAddressToString(address)] =
+					internalValues.NewPlcByteArray(utils.Int8ArrayToByteArray(int8s))
+			} else {
 				// Decode the data according to the fields type
 				rb := utils.NewReadBuffer(utils.Int8ArrayToUint8Array(int8s))
 				plcValue, err := driverModel.KnxDatapointParse(rb, field.GetTypeName())
+				// If any of the values doesn't decode correctly, we can't return any
 				if err != nil {
-					responseCodes[fieldName] = apiModel.PlcResponseCode_INVALID_DATA
-					plcValues[fieldName] = nil
-					continue
+					return apiModel.PlcResponseCode_INVALID_DATA, nil
 				}
-
-				// Add it to the result
-				responseCodes[fieldName] = apiModel.PlcResponseCode_OK
-				plcValues[fieldName] = plcValue
+				values[GroupAddressToString(address)] = plcValue
 			}
 		}
-		result := internalModel.NewDefaultPlcReadResponse(readRequest, responseCodes, plcValues)
-		resultChan <- apiModel.PlcReadRequestResult{
-			Request:  readRequest,
-			Response: result,
-			Err:      nil,
+
+		// Add it to the result
+		return apiModel.PlcResponseCode_OK, internalValues.NewPlcStruct(values)
+	} else {
+		// If it's not a pattern field, we can access the cached value a lot simpler
+
+		// Serialize the field to an uint16
+		wb := utils.NewWriteBuffer()
+		err := field.toGroupAddress().Serialize(*wb)
+		if err != nil {
+			return apiModel.PlcResponseCode_INVALID_ADDRESS, nil
 		}
-	}()
-	return resultChan
+		rawAddress := wb.GetBytes()
+		address := (uint16(rawAddress[0]) << 8) | uint16(rawAddress[1]&0xFF)
+
+		// Get the value form the cache
+		m.connection.valueCacheMutex.RLock()
+		int8s, ok := m.connection.valueCache[address]
+		m.connection.valueCacheMutex.RUnlock()
+		if !ok {
+			return apiModel.PlcResponseCode_NOT_FOUND, nil
+		}
+
+		// Decode the data according to the fields type
+		rb := utils.NewReadBuffer(utils.Int8ArrayToUint8Array(int8s))
+		plcValue, err := driverModel.KnxDatapointParse(rb, field.GetTypeName())
+		if err != nil {
+			return apiModel.PlcResponseCode_INVALID_DATA, nil
+		}
+
+		// Add it to the result
+		return apiModel.PlcResponseCode_OK, plcValue
+	}
 }
diff --git a/plc4go/internal/plc4go/knxnetip/Utils.go b/plc4go/internal/plc4go/knxnetip/Utils.go
index 5cc4d2e..5324f02 100644
--- a/plc4go/internal/plc4go/knxnetip/Utils.go
+++ b/plc4go/internal/plc4go/knxnetip/Utils.go
@@ -20,6 +20,7 @@ package knxnetip
 
 import (
 	driverModel "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
+	"github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
 	"strconv"
 )
 
@@ -39,3 +40,44 @@ func GroupAddressToString(groupAddress *driverModel.KnxGroupAddress) string {
 	}
 	return ""
 }
+
+func FieldToKnxAddress(field KnxNetIpField) *driverModel.KnxAddress {
+	if field.IsPatternField() {
+		return nil
+	}
+	var mainAddress int
+	var middleAddress int
+	var subAddress int
+	switch field.(type) {
+	case KnxNetIpDevicePropertyAddressPlcField:
+		plcField := field.(KnxNetIpDevicePropertyAddressPlcField)
+		mainAddress, _ = strconv.Atoi(plcField.MainGroup)
+		middleAddress, _ = strconv.Atoi(plcField.MiddleGroup)
+		subAddress, _ = strconv.Atoi(plcField.SubGroup)
+	case KnxNetIpGroupAddress3LevelPlcField:
+		plcField := field.(KnxNetIpGroupAddress3LevelPlcField)
+		mainAddress, _ = strconv.Atoi(plcField.MainGroup)
+		middleAddress, _ = strconv.Atoi(plcField.MiddleGroup)
+		subAddress, _ = strconv.Atoi(plcField.SubGroup)
+	default:
+		return nil
+	}
+
+	return driverModel.NewKnxAddress(uint8(mainAddress), uint8(middleAddress), uint8(subAddress))
+}
+
+func Int8ArrayToKnxAddress(data []int8) *driverModel.KnxAddress {
+	readBuffer := utils.NewReadBuffer(utils.Int8ArrayToUint8Array(data))
+	knxAddress, err := driverModel.KnxAddressParse(readBuffer)
+	if err != nil {
+		return nil
+	}
+	return knxAddress
+}
+
+func KnxAddressToInt8Array(knxAddress driverModel.KnxAddress) []int8 {
+	targetAddress := make([]int8, 2)
+	targetAddress[0] = int8((knxAddress.MainGroup&0xF)<<4 | (knxAddress.MiddleGroup & 0xF))
+	targetAddress[1] = int8(knxAddress.SubGroup)
+	return targetAddress
+}