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/27 21:56:20 UTC

[plc4x] branch develop updated: - Some minor bug-fixing of my latest changes

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 1a06b89  - Some minor bug-fixing of my latest changes
1a06b89 is described below

commit 1a06b89964cebcbfbb4dcc93d27af4b31c25a51c
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Wed Jan 27 22:56:12 2021 +0100

    - Some minor bug-fixing of my latest changes
---
 .../internal/plc4go/knxnetip/KnxNetIpConnection.go | 2145 ++++++++++----------
 1 file changed, 1079 insertions(+), 1066 deletions(-)

diff --git a/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go b/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go
index 5bdd243..d6c3c4f 100644
--- a/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go
+++ b/plc4go/internal/plc4go/knxnetip/KnxNetIpConnection.go
@@ -19,1213 +19,1226 @@
 package knxnetip
 
 import (
-    "bytes"
-    "errors"
-    "fmt"
-    driverModel "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
-    "github.com/apache/plc4x/plc4go/internal/plc4go/spi"
-    "github.com/apache/plc4x/plc4go/internal/plc4go/spi/interceptors"
-    internalModel "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
-    "github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports"
-    "github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports/udp"
-    "github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
-    "github.com/apache/plc4x/plc4go/pkg/plc4go"
-    apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
-    "github.com/apache/plc4x/plc4go/pkg/plc4go/values"
-    "math"
-    "net"
-    "strconv"
-    "strings"
-    "sync"
-    "sync/atomic"
-    "time"
+	"bytes"
+	"errors"
+	"fmt"
+	driverModel "github.com/apache/plc4x/plc4go/internal/plc4go/knxnetip/readwrite/model"
+	"github.com/apache/plc4x/plc4go/internal/plc4go/spi"
+	"github.com/apache/plc4x/plc4go/internal/plc4go/spi/interceptors"
+	internalModel "github.com/apache/plc4x/plc4go/internal/plc4go/spi/model"
+	"github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports"
+	"github.com/apache/plc4x/plc4go/internal/plc4go/spi/transports/udp"
+	"github.com/apache/plc4x/plc4go/internal/plc4go/spi/utils"
+	"github.com/apache/plc4x/plc4go/pkg/plc4go"
+	apiModel "github.com/apache/plc4x/plc4go/pkg/plc4go/model"
+	"github.com/apache/plc4x/plc4go/pkg/plc4go/values"
+	"math"
+	"net"
+	"strconv"
+	"strings"
+	"sync"
+	"sync/atomic"
+	"time"
 )
 
 type ConnectionMetadata struct {
-    KnxMedium         driverModel.KnxMedium
-    GatewayName       string
-    GatewayKnxAddress string
-    ClientKnxAddress  string
-
-    ProjectNumber          uint8
-    InstallationNumber     uint8
-    DeviceSerialNumber     []int8
-    DeviceMulticastAddress []int8
-    DeviceMacAddress       []int8
-    SupportedServices      []string
-
-    apiModel.PlcConnectionMetadata
+	KnxMedium         driverModel.KnxMedium
+	GatewayName       string
+	GatewayKnxAddress string
+	ClientKnxAddress  string
+
+	ProjectNumber          uint8
+	InstallationNumber     uint8
+	DeviceSerialNumber     []int8
+	DeviceMulticastAddress []int8
+	DeviceMacAddress       []int8
+	SupportedServices      []string
+
+	apiModel.PlcConnectionMetadata
 }
 
 func (m ConnectionMetadata) GetConnectionAttributes() map[string]string {
-    return map[string]string{
-        "KnxMedium":         m.KnxMedium.String(),
-        "GatewayName":       m.GatewayName,
-        "GatewayKnxAddress": m.GatewayKnxAddress,
-        "ClientKnxAddress":  m.ClientKnxAddress,
-
-        "ProjectNumber":          strconv.Itoa(int(m.ProjectNumber)),
-        "InstallationNumber":     strconv.Itoa(int(m.InstallationNumber)),
-        "DeviceSerialNumber":     utils.Int8ArrayToString(m.DeviceSerialNumber, " "),
-        "DeviceMulticastAddress": utils.Int8ArrayToString(m.DeviceSerialNumber, "."),
-        "DeviceMacAddress":       utils.Int8ArrayToString(m.DeviceSerialNumber, ":"),
-        "SupportedServices":      strings.Join(m.SupportedServices, ", "),
-    }
+	return map[string]string{
+		"KnxMedium":         m.KnxMedium.String(),
+		"GatewayName":       m.GatewayName,
+		"GatewayKnxAddress": m.GatewayKnxAddress,
+		"ClientKnxAddress":  m.ClientKnxAddress,
+
+		"ProjectNumber":          strconv.Itoa(int(m.ProjectNumber)),
+		"InstallationNumber":     strconv.Itoa(int(m.InstallationNumber)),
+		"DeviceSerialNumber":     utils.Int8ArrayToString(m.DeviceSerialNumber, " "),
+		"DeviceMulticastAddress": utils.Int8ArrayToString(m.DeviceSerialNumber, "."),
+		"DeviceMacAddress":       utils.Int8ArrayToString(m.DeviceSerialNumber, ":"),
+		"SupportedServices":      strings.Join(m.SupportedServices, ", "),
+	}
 }
 
 func (m ConnectionMetadata) CanRead() bool {
-    return true
+	return true
 }
 
 func (m ConnectionMetadata) CanWrite() bool {
-    return true
+	return true
 }
 
 func (m ConnectionMetadata) CanSubscribe() bool {
-    return true
+	return true
 }
 
 func (m ConnectionMetadata) CanBrowse() bool {
-    return true
+	return true
 }
 
 type KnxDeviceConnection struct {
-    counter          uint8
-    deviceDescriptor uint16
-    maxApdu          uint16
+	counter          uint8
+	deviceDescriptor uint16
+	maxApdu          uint16
 }
 
 type KnxNetIpConnection struct {
-    messageCodec             spi.MessageCodec
-    options                  map[string][]string
-    fieldHandler             spi.PlcFieldHandler
-    valueHandler             spi.PlcValueHandler
-    connectionStateTimer     *time.Ticker
-    quitConnectionStateTimer chan struct{}
-    subscribers              []*KnxNetIpSubscriber
-    leve3AddressCache        map[uint16]*driverModel.KnxGroupAddress3Level
-    leve2AddressCache        map[uint16]*driverModel.KnxGroupAddress2Level
-    leve1AddressCache        map[uint16]*driverModel.KnxGroupAddressFreeLevel
-
-    valueCache      map[uint16][]int8
-    valueCacheMutex sync.RWMutex
-    metadata        *ConnectionMetadata
-    defaultTtl      time.Duration
-
-    GatewayKnxAddress             *driverModel.KnxAddress
-    ClientKnxAddress              *driverModel.KnxAddress
-    CommunicationChannelId        uint8
-    SequenceCounter               int32
-    TunnelingRequestExpectationId int32
-    DeviceConnections             map[driverModel.KnxAddress]*KnxDeviceConnection
-
-    requestInterceptor internalModel.RequestInterceptor
-    plc4go.PlcConnection
+	messageCodec             spi.MessageCodec
+	options                  map[string][]string
+	fieldHandler             spi.PlcFieldHandler
+	valueHandler             spi.PlcValueHandler
+	connectionStateTimer     *time.Ticker
+	quitConnectionStateTimer chan struct{}
+	subscribers              []*KnxNetIpSubscriber
+	leve3AddressCache        map[uint16]*driverModel.KnxGroupAddress3Level
+	leve2AddressCache        map[uint16]*driverModel.KnxGroupAddress2Level
+	leve1AddressCache        map[uint16]*driverModel.KnxGroupAddressFreeLevel
+
+	valueCache      map[uint16][]int8
+	valueCacheMutex sync.RWMutex
+	metadata        *ConnectionMetadata
+	defaultTtl      time.Duration
+
+	GatewayKnxAddress             *driverModel.KnxAddress
+	ClientKnxAddress              *driverModel.KnxAddress
+	CommunicationChannelId        uint8
+	SequenceCounter               int32
+	TunnelingRequestExpectationId int32
+	DeviceConnections             map[driverModel.KnxAddress]*KnxDeviceConnection
+
+	requestInterceptor internalModel.RequestInterceptor
+	plc4go.PlcConnection
 }
 
 type InternalKnxNetIpConnection interface {
-    Send(request *driverModel.KnxNetIpMessage) error
-    SendRequest(request *driverModel.KnxNetIpMessage, expect func(response interface{}) (bool, bool)) (int32, chan interface{})
+	Send(request *driverModel.KnxNetIpMessage) error
+	SendRequest(request *driverModel.KnxNetIpMessage, expect func(response interface{}) (bool, bool)) (int32, chan interface{})
 }
 
 type KnxReadResult struct {
-    returnCode apiModel.PlcResponseCode
-    value      *values.PlcValue
+	returnCode apiModel.PlcResponseCode
+	value      *values.PlcValue
 }
 
 func NewKnxNetIpConnection(transportInstance transports.TransportInstance, options map[string][]string, fieldHandler spi.PlcFieldHandler) *KnxNetIpConnection {
-    connection := &KnxNetIpConnection{
-        options:            options,
-        fieldHandler:       fieldHandler,
-        valueHandler:       NewValueHandler(),
-        requestInterceptor: interceptors.NewSingleItemRequestInterceptor(),
-        subscribers:        []*KnxNetIpSubscriber{},
-        leve3AddressCache:  map[uint16]*driverModel.KnxGroupAddress3Level{},
-        leve2AddressCache:  map[uint16]*driverModel.KnxGroupAddress2Level{},
-        leve1AddressCache:  map[uint16]*driverModel.KnxGroupAddressFreeLevel{},
-        valueCache:         map[uint16][]int8{},
-        valueCacheMutex:    sync.RWMutex{},
-        metadata:           &ConnectionMetadata{},
-        defaultTtl:         time.Millisecond * 5000,
-        DeviceConnections:  map[driverModel.KnxAddress]*KnxDeviceConnection{},
-    }
-    connection.messageCodec = NewKnxNetIpMessageCodec(transportInstance, connection.interceptIncomingMessage)
-    return connection
+	connection := &KnxNetIpConnection{
+		options:            options,
+		fieldHandler:       fieldHandler,
+		valueHandler:       NewValueHandler(),
+		requestInterceptor: interceptors.NewSingleItemRequestInterceptor(),
+		subscribers:        []*KnxNetIpSubscriber{},
+		leve3AddressCache:  map[uint16]*driverModel.KnxGroupAddress3Level{},
+		leve2AddressCache:  map[uint16]*driverModel.KnxGroupAddress2Level{},
+		leve1AddressCache:  map[uint16]*driverModel.KnxGroupAddressFreeLevel{},
+		valueCache:         map[uint16][]int8{},
+		valueCacheMutex:    sync.RWMutex{},
+		metadata:           &ConnectionMetadata{},
+		defaultTtl:         time.Millisecond * 5000,
+		DeviceConnections:  map[driverModel.KnxAddress]*KnxDeviceConnection{},
+	}
+	connection.messageCodec = NewKnxNetIpMessageCodec(transportInstance, connection.interceptIncomingMessage)
+	return connection
 }
 
 func (m *KnxNetIpConnection) Connect() <-chan plc4go.PlcConnectionConnectResult {
-    result := make(chan plc4go.PlcConnectionConnectResult)
-    sendResult := func(connection plc4go.PlcConnection, err error) {
-        select {
-        case result <- plc4go.NewPlcConnectionConnectResult(connection, err):
-        }
-    }
-
-    go func() {
-        err := m.messageCodec.Connect()
-        if err != nil {
-            sendResult(nil, err)
-            return
-        }
-
-        searchResponseChannel := m.sendGatewaySearchRequest()
-        select {
-        case searchResponse := <-searchResponseChannel:
-            // Save some important information
-            m.metadata.KnxMedium = searchResponse.DibDeviceInfo.KnxMedium
-            m.metadata.GatewayName = string(bytes.Trim(utils.Int8ArrayToByteArray(
-                searchResponse.DibDeviceInfo.DeviceFriendlyName), "\x00"))
-            m.GatewayKnxAddress = searchResponse.DibDeviceInfo.KnxAddress
-            m.metadata.GatewayKnxAddress = KnxAddressToString(m.GatewayKnxAddress)
-            m.metadata.ProjectNumber = searchResponse.DibDeviceInfo.ProjectInstallationIdentifier.ProjectNumber
-            m.metadata.InstallationNumber = searchResponse.DibDeviceInfo.ProjectInstallationIdentifier.InstallationNumber
-            m.metadata.DeviceSerialNumber = searchResponse.DibDeviceInfo.KnxNetIpDeviceSerialNumber
-            m.metadata.DeviceMulticastAddress = searchResponse.DibDeviceInfo.KnxNetIpDeviceMulticastAddress.Addr
-            m.metadata.DeviceMacAddress = searchResponse.DibDeviceInfo.KnxNetIpDeviceMacAddress.Addr
-            m.metadata.SupportedServices = []string{}
-            supportsTunneling := false
-            for _, serviceId := range searchResponse.DibSuppSvcFamilies.ServiceIds {
-                m.metadata.SupportedServices = append(m.metadata.SupportedServices, serviceId.Child.GetTypeName())
-                // If this is an instance of the "tunneling", service, this connection supports tunneling
-                _, ok := serviceId.Child.(*driverModel.KnxNetIpTunneling)
-                if ok {
-                    supportsTunneling = true
-                    break
-                }
-            }
-
-            // If the current device supports tunneling, create a tunneling connection.
-            // Via this connection we then get access to the entire KNX network this Gateway is connected to.
-            if supportsTunneling {
-                // As soon as we got a successful search-response back, send a connection request.
-                connectionResponseChannel := m.sendGatewayConnectionRequest()
-                select {
-                case connectionResponse := <-connectionResponseChannel:
-                    // Save the communication channel id
-                    m.CommunicationChannelId = connectionResponse.CommunicationChannelId
-
-                    // Reset the sequence counter
-                    m.SequenceCounter = -1
-
-                    // If the connection was successful, the gateway will now forward any packets
-                    // on the KNX bus that are broadcast packets to us, so we have to setup things
-                    // to handle these incoming messages.
-                    switch connectionResponse.Status {
-                    case driverModel.Status_NO_ERROR:
-                        // Save the KNX Address the Gateway assigned to us for this connection.
-                        tunnelConnectionDataBlock :=
-                            driverModel.CastConnectionResponseDataBlockTunnelConnection(
-                                connectionResponse.ConnectionResponseDataBlock)
-                        m.ClientKnxAddress = tunnelConnectionDataBlock.KnxAddress
-
-                        // Start a timer that sends connection-state requests every 60 seconds
-                        m.connectionStateTimer = time.NewTicker(60 * time.Second)
-                        m.quitConnectionStateTimer = make(chan struct{})
-                        go func() {
-                            for {
-                                select {
-                                case <-m.connectionStateTimer.C:
-                                    // We're using the connection-state-request as ping operation ...
-                                    ping := m.Ping()
-                                    select {
-                                    case pingResult := <-ping:
-                                        if pingResult.Err != nil {
-                                            // TODO: Do some error handling here ...
-                                            m.connectionStateTimer.Stop()
-                                        }
-                                    case <-time.After(5 * time.Second):
-                                        // Close the connection
-                                        m.Close()
-                                    }
-
-                                // If externally a request to stop the timer was issued, stop the timer.
-                                case <-m.quitConnectionStateTimer:
-                                    // TODO: Do some error handling here ...
-                                    m.connectionStateTimer.Stop()
-                                    return
-                                }
-                            }
-                        }()
-
-                        // Create a go routine to handle incoming tunneling-requests which haven't been
-                        // handled by any other handler. This is where usually the GroupValueWrite messages
-                        // are being handled.
-                        go func() {
-                            defaultIncomingMessageChannel := m.messageCodec.GetDefaultIncomingMessageChannel()
-                            for {
-                                incomingMessage := <-defaultIncomingMessageChannel
-                                tunnelingRequest := driverModel.CastTunnelingRequest(incomingMessage)
-                                if tunnelingRequest == nil {
-                                    tunnelingResponse := driverModel.CastTunnelingResponse(incomingMessage)
-                                    if tunnelingResponse != nil {
-                                        fmt.Printf("Got an unhandled TunnelingResponse message %v\n", tunnelingResponse)
-                                    } else {
-                                        fmt.Printf("Not a TunnelingRequest message %v\n", incomingMessage)
-                                    }
-                                } else {
-                                    if tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
-                                        fmt.Printf("Not for this connection %v\n", tunnelingRequest)
-                                        continue
-                                    }
-
-                                    lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
-                                    if lDataInd != nil {
-                                        // Get APDU, source and target address
-                                        var apdu *driverModel.Apdu
-                                        var sourceAddress *driverModel.KnxAddress
-                                        //var targetAddress []int8
-                                        lDataFrameData := driverModel.CastLDataFrameData(lDataInd.DataFrame)
-                                        if lDataFrameData != nil {
-                                            apdu = lDataFrameData.Apdu
-                                            sourceAddress = lDataFrameData.SourceAddress
-                                            //targetAddress = lDataFrameData.DestinationAddress
-                                        } else {
-                                            lDataFrameDataExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
-                                            if lDataFrameDataExt != nil {
-                                                apdu = lDataFrameDataExt.Apdu
-                                                sourceAddress = lDataFrameDataExt.SourceAddress
-                                                //targetAddress = lDataFrameDataExt.DestinationAddress
-                                            }
-                                        }
-
-                                        // If this is not an APDU, there is no need to further handle it.
-                                        if apdu == nil {
-                                            continue
-                                        }
-
-                                        // If this is an incoming disconnect request, remove the device
-                                        // from the device connections, otherwise handle it as normal
-                                        // incoming message.
-                                        apduControlContainer := driverModel.CastApduControlContainer(apdu)
-                                        if apduControlContainer != nil {
-                                            disconnectApdu := driverModel.CastApduControlDisconnect(apduControlContainer.ControlApdu)
-                                            if disconnectApdu != nil {
-                                                if m.DeviceConnections[*sourceAddress] != nil /* && m.ClientKnxAddress == Int8ArrayToKnxAddress(targetAddress)*/ {
-                                                    // Remove the connection
-                                                    delete(m.DeviceConnections, *sourceAddress)
-                                                }
-                                            }
-                                        } else {
-                                            m.handleIncomingTunnelingRequest(tunnelingRequest)
-                                        }
-                                    }
-                                }
-                            }
-                        }()
-
-                        // Fire the "connected" event
-                        sendResult(m, nil)
-                    case driverModel.Status_NO_MORE_CONNECTIONS:
-                        sendResult(nil, errors.New("no more connections"))
-                    }
-                }
-            }
-        }
-    }()
-    return result
+	result := make(chan plc4go.PlcConnectionConnectResult)
+	sendResult := func(connection plc4go.PlcConnection, err error) {
+		select {
+		case result <- plc4go.NewPlcConnectionConnectResult(connection, err):
+		}
+	}
+
+	go func() {
+		err := m.messageCodec.Connect()
+		if err != nil {
+			sendResult(nil, err)
+			return
+		}
+
+		searchResponseChannel := m.sendGatewaySearchRequest()
+		select {
+		case searchResponse := <-searchResponseChannel:
+			// Save some important information
+			m.metadata.KnxMedium = searchResponse.DibDeviceInfo.KnxMedium
+			m.metadata.GatewayName = string(bytes.Trim(utils.Int8ArrayToByteArray(
+				searchResponse.DibDeviceInfo.DeviceFriendlyName), "\x00"))
+			m.GatewayKnxAddress = searchResponse.DibDeviceInfo.KnxAddress
+			m.metadata.GatewayKnxAddress = KnxAddressToString(m.GatewayKnxAddress)
+			m.metadata.ProjectNumber = searchResponse.DibDeviceInfo.ProjectInstallationIdentifier.ProjectNumber
+			m.metadata.InstallationNumber = searchResponse.DibDeviceInfo.ProjectInstallationIdentifier.InstallationNumber
+			m.metadata.DeviceSerialNumber = searchResponse.DibDeviceInfo.KnxNetIpDeviceSerialNumber
+			m.metadata.DeviceMulticastAddress = searchResponse.DibDeviceInfo.KnxNetIpDeviceMulticastAddress.Addr
+			m.metadata.DeviceMacAddress = searchResponse.DibDeviceInfo.KnxNetIpDeviceMacAddress.Addr
+			m.metadata.SupportedServices = []string{}
+			supportsTunneling := false
+			for _, serviceId := range searchResponse.DibSuppSvcFamilies.ServiceIds {
+				m.metadata.SupportedServices = append(m.metadata.SupportedServices, serviceId.Child.GetTypeName())
+				// If this is an instance of the "tunneling", service, this connection supports tunneling
+				_, ok := serviceId.Child.(*driverModel.KnxNetIpTunneling)
+				if ok {
+					supportsTunneling = true
+					break
+				}
+			}
+
+			// If the current device supports tunneling, create a tunneling connection.
+			// Via this connection we then get access to the entire KNX network this Gateway is connected to.
+			if supportsTunneling {
+				// As soon as we got a successful search-response back, send a connection request.
+				connectionResponseChannel := m.sendGatewayConnectionRequest()
+				select {
+				case connectionResponse := <-connectionResponseChannel:
+					// Save the communication channel id
+					m.CommunicationChannelId = connectionResponse.CommunicationChannelId
+
+					// Reset the sequence counter
+					m.SequenceCounter = -1
+
+					// If the connection was successful, the gateway will now forward any packets
+					// on the KNX bus that are broadcast packets to us, so we have to setup things
+					// to handle these incoming messages.
+					switch connectionResponse.Status {
+					case driverModel.Status_NO_ERROR:
+						// Save the KNX Address the Gateway assigned to us for this connection.
+						tunnelConnectionDataBlock :=
+							driverModel.CastConnectionResponseDataBlockTunnelConnection(
+								connectionResponse.ConnectionResponseDataBlock)
+						m.ClientKnxAddress = tunnelConnectionDataBlock.KnxAddress
+
+						// Start a timer that sends connection-state requests every 60 seconds
+						m.connectionStateTimer = time.NewTicker(60 * time.Second)
+						m.quitConnectionStateTimer = make(chan struct{})
+						go func() {
+							for {
+								select {
+								case <-m.connectionStateTimer.C:
+									// We're using the connection-state-request as ping operation ...
+									ping := m.Ping()
+									select {
+									case pingResult := <-ping:
+										if pingResult.Err != nil {
+											// TODO: Do some error handling here ...
+											m.connectionStateTimer.Stop()
+										}
+									case <-time.After(5 * time.Second):
+										// Close the connection
+										m.Close()
+									}
+
+								// If externally a request to stop the timer was issued, stop the timer.
+								case <-m.quitConnectionStateTimer:
+									// TODO: Do some error handling here ...
+									m.connectionStateTimer.Stop()
+									return
+								}
+							}
+						}()
+
+						// Create a go routine to handle incoming tunneling-requests which haven't been
+						// handled by any other handler. This is where usually the GroupValueWrite messages
+						// are being handled.
+						go func() {
+							defaultIncomingMessageChannel := m.messageCodec.GetDefaultIncomingMessageChannel()
+							for {
+								incomingMessage := <-defaultIncomingMessageChannel
+								tunnelingRequest := driverModel.CastTunnelingRequest(incomingMessage)
+								if tunnelingRequest == nil {
+									tunnelingResponse := driverModel.CastTunnelingResponse(incomingMessage)
+									if tunnelingResponse != nil {
+										fmt.Printf("Got an unhandled TunnelingResponse message %v\n", tunnelingResponse)
+									} else {
+										fmt.Printf("Not a TunnelingRequest message %v\n", incomingMessage)
+									}
+								} else {
+									if tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+										fmt.Printf("Not for this connection %v\n", tunnelingRequest)
+										continue
+									}
+
+									lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+									if lDataInd != nil {
+										// Get APDU, source and target address
+										var apdu *driverModel.Apdu
+										var sourceAddress *driverModel.KnxAddress
+										//var targetAddress []int8
+										lDataFrameData := driverModel.CastLDataFrameData(lDataInd.DataFrame)
+										if lDataFrameData != nil {
+											apdu = lDataFrameData.Apdu
+											sourceAddress = lDataFrameData.SourceAddress
+											//targetAddress = lDataFrameData.DestinationAddress
+										} else {
+											lDataFrameDataExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+											if lDataFrameDataExt != nil {
+												apdu = lDataFrameDataExt.Apdu
+												sourceAddress = lDataFrameDataExt.SourceAddress
+												//targetAddress = lDataFrameDataExt.DestinationAddress
+											}
+										}
+
+										// If this is not an APDU, there is no need to further handle it.
+										if apdu == nil {
+											continue
+										}
+
+										// If this is an incoming disconnect request, remove the device
+										// from the device connections, otherwise handle it as normal
+										// incoming message.
+										apduControlContainer := driverModel.CastApduControlContainer(apdu)
+										if apduControlContainer != nil {
+											disconnectApdu := driverModel.CastApduControlDisconnect(apduControlContainer.ControlApdu)
+											if disconnectApdu != nil {
+												if m.DeviceConnections[*sourceAddress] != nil /* && m.ClientKnxAddress == Int8ArrayToKnxAddress(targetAddress)*/ {
+													// Remove the connection
+													delete(m.DeviceConnections, *sourceAddress)
+												}
+											}
+										} else {
+											m.handleIncomingTunnelingRequest(tunnelingRequest)
+										}
+									}
+								}
+							}
+						}()
+
+						// Fire the "connected" event
+						sendResult(m, nil)
+					case driverModel.Status_NO_MORE_CONNECTIONS:
+						sendResult(nil, errors.New("no more connections"))
+					}
+				}
+			}
+		}
+	}()
+	return result
 }
 
 func (m *KnxNetIpConnection) Close() <-chan plc4go.PlcConnectionCloseResult {
-    result := make(chan plc4go.PlcConnectionCloseResult)
-    sendResult := func(connection plc4go.PlcConnection, err error) {
-        select {
-        case result <- plc4go.NewPlcConnectionCloseResult(connection, err):
-        }
-    }
-
-    go func() {
-        // Stop the connection-state checker.
-        m.connectionStateTimer.Stop()
-
-        // Disconnect from all knx devices we are still connected to.
-        for address := range m.DeviceConnections {
-            disconnects := m.sendDeviceDisconnectionRequest(address)
-            select {
-            case _ = <-disconnects:
-                delete(m.DeviceConnections, address)
-            }
-        }
-
-        // Send a disconnect request from the gateway.
-        disconnectionResponseChannel := m.sendGatewayDisconnectionRequest()
-        select {
-        case disconnectResponse := <-disconnectionResponseChannel:
-            if disconnectResponse.Status == driverModel.Status_NO_ERROR {
-                sendResult(m, nil)
-            } else {
-                sendResult(m, errors.New("got an unexpected response for disconnect "+disconnectResponse.Status.String()))
-            }
-        }
-    }()
-    return result
+	result := make(chan plc4go.PlcConnectionCloseResult)
+	sendResult := func(connection plc4go.PlcConnection, err error) {
+		select {
+		case result <- plc4go.NewPlcConnectionCloseResult(connection, err):
+		}
+	}
+
+	go func() {
+		// Stop the connection-state checker.
+		m.connectionStateTimer.Stop()
+
+		// Disconnect from all knx devices we are still connected to.
+		for address := range m.DeviceConnections {
+			disconnects := m.sendDeviceDisconnectionRequest(address)
+			select {
+			case _ = <-disconnects:
+				delete(m.DeviceConnections, address)
+			}
+		}
+
+		// Send a disconnect request from the gateway.
+		disconnectionResponseChannel := m.sendGatewayDisconnectionRequest()
+		select {
+		case disconnectResponse := <-disconnectionResponseChannel:
+			if disconnectResponse.Status == driverModel.Status_NO_ERROR {
+				sendResult(m, nil)
+			} else {
+				sendResult(m, errors.New("got an unexpected response for disconnect "+disconnectResponse.Status.String()))
+			}
+		}
+	}()
+	return result
 }
 
 func (m *KnxNetIpConnection) IsConnected() bool {
-    if m.messageCodec != nil {
-        pingChannel := m.Ping()
-        select {
-        case pingResponse := <-pingChannel:
-            return pingResponse.Err == nil
-        case <-time.After(time.Second * 5):
-            return false
-        }
-    }
-    return false
+	if m.messageCodec != nil {
+		pingChannel := m.Ping()
+		select {
+		case pingResponse := <-pingChannel:
+			return pingResponse.Err == nil
+		case <-time.After(time.Second * 5):
+			return false
+		}
+	}
+	return false
 }
 
 func (m *KnxNetIpConnection) Ping() <-chan plc4go.PlcConnectionPingResult {
-    result := make(chan plc4go.PlcConnectionPingResult)
-    sendResult := func(err error) {
-        select {
-        case result <- plc4go.NewPlcConnectionPingResult(err):
-        }
-    }
-
-    go func() {
-        // Send the connection state request
-        connectionStateResponseChannel := m.sendConnectionStateRequest()
-        select {
-        case connectionStateResponse := <-connectionStateResponseChannel:
-            if connectionStateResponse.Status != driverModel.Status_NO_ERROR {
-                sendResult(errors.New("got a failure response code " + strconv.Itoa(int(connectionStateResponse.Status))))
-            } else {
-                sendResult(nil)
-            }
-        }
-        return
-    }()
-    return result
+	result := make(chan plc4go.PlcConnectionPingResult)
+	sendResult := func(err error) {
+		select {
+		case result <- plc4go.NewPlcConnectionPingResult(err):
+		}
+	}
+
+	go func() {
+		// Send the connection state request
+		connectionStateResponseChannel := m.sendConnectionStateRequest()
+		select {
+		case connectionStateResponse := <-connectionStateResponseChannel:
+			if connectionStateResponse.Status != driverModel.Status_NO_ERROR {
+				sendResult(errors.New("got a failure response code " + strconv.Itoa(int(connectionStateResponse.Status))))
+			} else {
+				sendResult(nil)
+			}
+		}
+		return
+	}()
+	return result
 }
 
 func (m *KnxNetIpConnection) ConnectToDevice(targetAddress driverModel.KnxAddress) <-chan *KnxDeviceConnection {
-    result := make(chan *KnxDeviceConnection)
-
-    // If we're already connected, use that connection instead.
-    if connection, ok := m.DeviceConnections[targetAddress]; ok {
-        result <- connection
-        return result
-    }
-
-    // First send a connection request
-    go func() {
-        controlConnectResponseChannel := m.sendDeviceConnectionRequest(targetAddress)
-        select {
-        case _ = <-controlConnectResponseChannel:
-            // If the connection request was successful, try to read the device-descriptor
-            deviceDescriptorResponses := m.sendDeviceDeviceDescriptorReadRequest(targetAddress)
-            select {
-            case _ = <-deviceDescriptorResponses:
-                // Last, not least, read the max APDU size
-                propertyValueResponses := m.sendDevicePropertyReadRequest(targetAddress, 0, 56)
-                select {
-                case propertyValueResponse := <-propertyValueResponses:
-                    // If we were able to read the max APDU size, then use the minimum of
-                    // the connection APDU size and the device APDU size, otherwise use the
-                    // default APDU Size
-                    deviceApduSize := uint16(100)
-                    if propertyValueResponse.returnCode == apiModel.PlcResponseCode_OK {
-                        deviceApduSize = (*propertyValueResponse.value).GetUint16()
-                    }
-                    connection := m.DeviceConnections[targetAddress]
-                    connection.maxApdu = uint16(math.Min(float64(deviceApduSize), 240))
-                    result <- connection
-                }
-            }
-        }
-    }()
-
-    return result
+	result := make(chan *KnxDeviceConnection)
+
+	// If we're already connected, use that connection instead.
+	if connection, ok := m.DeviceConnections[targetAddress]; ok {
+		result <- connection
+		return result
+	}
+
+	// First send a connection request
+	go func() {
+		controlConnectResponseChannel := m.sendDeviceConnectionRequest(targetAddress)
+		select {
+		case conResult := <-controlConnectResponseChannel:
+			if conResult == nil {
+				result <- nil
+				return
+			}
+
+			// If the connection request was successful, try to read the device-descriptor
+			deviceDescriptorResponses := m.sendDeviceDeviceDescriptorReadRequest(targetAddress)
+			select {
+			case _ = <-deviceDescriptorResponses:
+				// Last, not least, read the max APDU size
+				propertyValueResponses := m.sendDevicePropertyReadRequest(targetAddress, 0, 56)
+				select {
+				case propertyValueResponse := <-propertyValueResponses:
+					// If we were able to read the max APDU size, then use the minimum of
+					// the connection APDU size and the device APDU size, otherwise use the
+					// default APDU Size
+					deviceApduSize := uint16(100)
+					if propertyValueResponse.returnCode == apiModel.PlcResponseCode_OK {
+						deviceApduSize = (*propertyValueResponse.value).GetUint16()
+					}
+					connection := m.DeviceConnections[targetAddress]
+					connection.maxApdu = uint16(math.Min(float64(deviceApduSize), 240))
+					result <- connection
+				}
+			}
+		}
+	}()
+
+	return result
 }
 
 func (m *KnxNetIpConnection) DisconnectFromDevice(targetAddress driverModel.KnxAddress) <-chan *KnxDeviceConnection {
-    result := make(chan *KnxDeviceConnection)
-
-    if connection, ok := m.DeviceConnections[targetAddress]; ok {
-        disconnects := m.sendDeviceDisconnectionRequest(targetAddress)
-        select {
-        case _ = <-disconnects:
-            result <- connection
-        }
-    } else {
-        result <- nil
-    }
-
-    return result
+	result := make(chan *KnxDeviceConnection)
+
+	if connection, ok := m.DeviceConnections[targetAddress]; ok {
+		disconnects := m.sendDeviceDisconnectionRequest(targetAddress)
+		select {
+		case _ = <-disconnects:
+			result <- connection
+		}
+	} else {
+		result <- nil
+	}
+
+	return result
 }
 
 func (m *KnxNetIpConnection) ReadDeviceProperty(targetAddress driverModel.KnxAddress, objectId uint8, propertyId uint8) <-chan KnxReadResult {
-    result := make(chan KnxReadResult)
-
-    go func() {
-        // Check if there is already a connection available,
-        // if not, create a new one.
-        connection, ok := m.DeviceConnections[targetAddress]
-        if !ok {
-            connections := m.ConnectToDevice(targetAddress)
-            select {
-            case connection = <-connections:
-            }
-        }
-
-        // If we successfully got a connection, read the property
-        if connection != nil {
-            propertyValueResponses := m.sendDevicePropertyReadRequest(targetAddress, objectId, propertyId)
-            select {
-            case propertyValueResponse := <-propertyValueResponses:
-                result <- propertyValueResponse
-            }
-        }
-    }()
-
-    return result
+	result := make(chan KnxReadResult)
+
+	go func() {
+		// Check if there is already a connection available,
+		// if not, create a new one.
+		connection, ok := m.DeviceConnections[targetAddress]
+		if !ok {
+			connections := m.ConnectToDevice(targetAddress)
+			select {
+			case connection = <-connections:
+				// If we didn't get a connect, abort
+				if connection == nil {
+					result <- KnxReadResult{
+						returnCode: apiModel.PlcResponseCode_INVALID_ADDRESS,
+						value:      nil,
+					}
+				}
+			}
+		}
+
+		// If we successfully got a connection, read the property
+		if connection != nil {
+			propertyValueResponses := m.sendDevicePropertyReadRequest(targetAddress, objectId, propertyId)
+			select {
+			case propertyValueResponse := <-propertyValueResponses:
+				result <- propertyValueResponse
+			}
+		}
+	}()
+
+	return result
 }
 
 func (m *KnxNetIpConnection) GetMetadata() apiModel.PlcConnectionMetadata {
-    return m.metadata
+	return m.metadata
 }
 
 func (m *KnxNetIpConnection) ReadRequestBuilder() apiModel.PlcReadRequestBuilder {
-    return internalModel.NewDefaultPlcReadRequestBuilder(
-        m.fieldHandler, NewKnxNetIpReader(m))
+	return internalModel.NewDefaultPlcReadRequestBuilder(
+		m.fieldHandler, NewKnxNetIpReader(m))
 }
 
 func (m *KnxNetIpConnection) WriteRequestBuilder() apiModel.PlcWriteRequestBuilder {
-    return internalModel.NewDefaultPlcWriteRequestBuilder(
-        m.fieldHandler, m.valueHandler, NewKnxNetIpWriter(m.messageCodec))
+	return internalModel.NewDefaultPlcWriteRequestBuilder(
+		m.fieldHandler, m.valueHandler, NewKnxNetIpWriter(m.messageCodec))
 }
 
 func (m *KnxNetIpConnection) SubscriptionRequestBuilder() apiModel.PlcSubscriptionRequestBuilder {
-    return internalModel.NewDefaultPlcSubscriptionRequestBuilder(
-        m.fieldHandler, m.valueHandler, NewKnxNetIpSubscriber(m))
+	return internalModel.NewDefaultPlcSubscriptionRequestBuilder(
+		m.fieldHandler, m.valueHandler, NewKnxNetIpSubscriber(m))
 }
 
 func (m *KnxNetIpConnection) BrowseRequestBuilder() apiModel.PlcBrowseRequestBuilder {
-    return internalModel.NewDefaultPlcBrowseRequestBuilder(NewKnxNetIpBrowser(m, m.messageCodec))
+	return internalModel.NewDefaultPlcBrowseRequestBuilder(NewKnxNetIpBrowser(m, m.messageCodec))
 }
 
 func (m *KnxNetIpConnection) UnsubscriptionRequestBuilder() apiModel.PlcUnsubscriptionRequestBuilder {
-    return nil /*internalModel.NewDefaultPlcUnsubscriptionRequestBuilder(
-      m.fieldHandler, m.valueHandler, NewKnxNetIpSubscriber(m.messageCodec))*/
+	return nil /*internalModel.NewDefaultPlcUnsubscriptionRequestBuilder(
+	  m.fieldHandler, m.valueHandler, NewKnxNetIpSubscriber(m.messageCodec))*/
 }
 
 func (m *KnxNetIpConnection) GetTransportInstance() transports.TransportInstance {
-    if mc, ok := m.messageCodec.(spi.TransportInstanceExposer); ok {
-        return mc.GetTransportInstance()
-    }
-    return nil
+	if mc, ok := m.messageCodec.(spi.TransportInstanceExposer); ok {
+		return mc.GetTransportInstance()
+	}
+	return nil
 }
 
 func (m *KnxNetIpConnection) GetPlcFieldHandler() spi.PlcFieldHandler {
-    return m.fieldHandler
+	return m.fieldHandler
 }
 
 func (m *KnxNetIpConnection) GetPlcValueHandler() spi.PlcValueHandler {
-    return m.valueHandler
+	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)
+	// 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) sendGatewaySearchRequest() chan driverModel.SearchResponse {
-    result := make(chan driverModel.SearchResponse)
-    localAddress, err := m.getLocalAddress()
-    if err != nil {
-        close(result)
-        return result
-    }
-    localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP))
-    discoveryEndpoint := driverModel.NewHPAIDiscoveryEndpoint(
-        driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port))
-    searchRequest := driverModel.NewSearchRequest(discoveryEndpoint)
-    err = m.messageCodec.SendRequest(searchRequest,
-        func(message interface{}) bool {
-            searchResponse := driverModel.CastSearchResponse(message)
-            return searchResponse != nil
-        },
-        func(message interface{}) error {
-            searchResponse := driverModel.CastSearchResponse(message)
-            result <- *searchResponse
-            close(result)
-            return nil
-        },
-        m.defaultTtl)
-    if err != nil {
-        close(result)
-    }
-    return result
+	result := make(chan driverModel.SearchResponse)
+	localAddress, err := m.getLocalAddress()
+	if err != nil {
+		close(result)
+		return result
+	}
+	localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP))
+	discoveryEndpoint := driverModel.NewHPAIDiscoveryEndpoint(
+		driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port))
+	searchRequest := driverModel.NewSearchRequest(discoveryEndpoint)
+	err = m.messageCodec.SendRequest(searchRequest,
+		func(message interface{}) bool {
+			searchResponse := driverModel.CastSearchResponse(message)
+			return searchResponse != nil
+		},
+		func(message interface{}) error {
+			searchResponse := driverModel.CastSearchResponse(message)
+			result <- *searchResponse
+			close(result)
+			return nil
+		},
+		m.defaultTtl)
+	if err != nil {
+		close(result)
+	}
+	return result
 }
 
 func (m *KnxNetIpConnection) sendGatewayConnectionRequest() chan driverModel.ConnectionResponse {
-    result := make(chan driverModel.ConnectionResponse)
-    localAddress, err := m.getLocalAddress()
-    if err != nil {
-        close(result)
-        return result
-    }
-    localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP)[len(localAddress.IP)-4:])
-    connectionRequest := driverModel.NewConnectionRequest(
-        driverModel.NewHPAIDiscoveryEndpoint(driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port)),
-        driverModel.NewHPAIDataEndpoint(driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port)),
-        driverModel.NewConnectionRequestInformationTunnelConnection(driverModel.KnxLayer_TUNNEL_LINK_LAYER),
-    )
-    err = m.messageCodec.SendRequest(connectionRequest,
-        func(message interface{}) bool {
-            connectionResponse := driverModel.CastConnectionResponse(message)
-            return connectionResponse != nil
-        },
-        func(message interface{}) error {
-            connectionResponse := driverModel.CastConnectionResponse(message)
-            result <- *connectionResponse
-            close(result)
-            return nil
-        },
-        m.defaultTtl)
-    if err != nil {
-        close(result)
-    }
-    return result
+	result := make(chan driverModel.ConnectionResponse)
+	localAddress, err := m.getLocalAddress()
+	if err != nil {
+		close(result)
+		return result
+	}
+	localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP)[len(localAddress.IP)-4:])
+	connectionRequest := driverModel.NewConnectionRequest(
+		driverModel.NewHPAIDiscoveryEndpoint(driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port)),
+		driverModel.NewHPAIDataEndpoint(driverModel.HostProtocolCode_IPV4_UDP, localAddr, uint16(localAddress.Port)),
+		driverModel.NewConnectionRequestInformationTunnelConnection(driverModel.KnxLayer_TUNNEL_LINK_LAYER),
+	)
+	err = m.messageCodec.SendRequest(connectionRequest,
+		func(message interface{}) bool {
+			connectionResponse := driverModel.CastConnectionResponse(message)
+			return connectionResponse != nil
+		},
+		func(message interface{}) error {
+			connectionResponse := driverModel.CastConnectionResponse(message)
+			result <- *connectionResponse
+			close(result)
+			return nil
+		},
+		m.defaultTtl)
+	if err != nil {
+		close(result)
+	}
+	return result
 }
 
 func (m *KnxNetIpConnection) sendGatewayDisconnectionRequest() chan driverModel.DisconnectResponse {
-    result := make(chan driverModel.DisconnectResponse)
-    localAddress, err := m.getLocalAddress()
-    if err != nil {
-        close(result)
-        return result
-    }
-    localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP)[len(localAddress.IP)-4:])
-    disconnectRequest := driverModel.NewDisconnectRequest(
-        m.CommunicationChannelId,
-        driverModel.NewHPAIControlEndpoint(
-            driverModel.HostProtocolCode_IPV4_UDP,
-            localAddr,
-            uint16(localAddress.Port)))
-    err = m.messageCodec.SendRequest(disconnectRequest,
-        func(message interface{}) bool {
-            disconnectResponse := driverModel.CastDisconnectResponse(message)
-            return disconnectResponse != nil
-        },
-        func(message interface{}) error {
-            disconnectResponse := driverModel.CastDisconnectResponse(message)
-            result <- *disconnectResponse
-            close(result)
-            return nil
-        },
-        m.defaultTtl)
-    if err != nil {
-        close(result)
-    }
-    return result
+	result := make(chan driverModel.DisconnectResponse)
+	localAddress, err := m.getLocalAddress()
+	if err != nil {
+		close(result)
+		return result
+	}
+	localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP)[len(localAddress.IP)-4:])
+	disconnectRequest := driverModel.NewDisconnectRequest(
+		m.CommunicationChannelId,
+		driverModel.NewHPAIControlEndpoint(
+			driverModel.HostProtocolCode_IPV4_UDP,
+			localAddr,
+			uint16(localAddress.Port)))
+	err = m.messageCodec.SendRequest(disconnectRequest,
+		func(message interface{}) bool {
+			disconnectResponse := driverModel.CastDisconnectResponse(message)
+			return disconnectResponse != nil
+		},
+		func(message interface{}) error {
+			disconnectResponse := driverModel.CastDisconnectResponse(message)
+			result <- *disconnectResponse
+			close(result)
+			return nil
+		},
+		m.defaultTtl)
+	if err != nil {
+		close(result)
+	}
+	return result
 }
 
 func (m *KnxNetIpConnection) sendConnectionStateRequest() chan driverModel.ConnectionStateResponse {
-    result := make(chan driverModel.ConnectionStateResponse)
-    localAddress, err := m.getLocalAddress()
-    if err != nil {
-        close(result)
-        return result
-    }
-    localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP)[len(localAddress.IP)-4:])
-    connectionStateRequest := driverModel.NewConnectionStateRequest(
-        m.CommunicationChannelId,
-        driverModel.NewHPAIControlEndpoint(
-            driverModel.HostProtocolCode_IPV4_UDP,
-            localAddr, uint16(localAddress.Port)))
-    err = m.messageCodec.SendRequest(connectionStateRequest,
-        func(message interface{}) bool {
-            connectionStateResponse := driverModel.CastConnectionStateResponse(message)
-            return connectionStateResponse != nil
-        },
-        func(message interface{}) error {
-            connectionStateResponse := driverModel.CastConnectionStateResponse(message)
-            result <- *connectionStateResponse
-            close(result)
-            return nil
-        },
-        m.defaultTtl)
-    if err != nil {
-        close(result)
-    }
-    return result
+	result := make(chan driverModel.ConnectionStateResponse)
+	localAddress, err := m.getLocalAddress()
+	if err != nil {
+		close(result)
+		return result
+	}
+	localAddr := driverModel.NewIPAddress(utils.ByteArrayToInt8Array(localAddress.IP)[len(localAddress.IP)-4:])
+	connectionStateRequest := driverModel.NewConnectionStateRequest(
+		m.CommunicationChannelId,
+		driverModel.NewHPAIControlEndpoint(
+			driverModel.HostProtocolCode_IPV4_UDP,
+			localAddr, uint16(localAddress.Port)))
+	err = m.messageCodec.SendRequest(connectionStateRequest,
+		func(message interface{}) bool {
+			connectionStateResponse := driverModel.CastConnectionStateResponse(message)
+			return connectionStateResponse != nil
+		},
+		func(message interface{}) error {
+			connectionStateResponse := driverModel.CastConnectionStateResponse(message)
+			result <- *connectionStateResponse
+			close(result)
+			return nil
+		},
+		m.defaultTtl)
+	if err != nil {
+		close(result)
+	}
+	return result
 }
 
-func (m *KnxNetIpConnection) sendDeviceConnectionRequest(targetAddress driverModel.KnxAddress) chan driverModel.ApduControlConnect {
-    result := make(chan driverModel.ApduControlConnect)
-
-    go func() {
-        // Send a connection request to the individual KNX device
-        deviceConnectionRequest := driverModel.NewTunnelingRequest(
-            driverModel.NewTunnelingRequestDataBlock(0, 0),
-            driverModel.NewLDataReq(0, nil,
-                driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
-                    driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
-                    driverModel.NewApduControlContainer(driverModel.NewApduControlConnect(), 0, false, 0),
-                    true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
-        err := m.sendRequest(
-            deviceConnectionRequest,
-            // The Gateway is now supposed to send an Ack to this request.
-            func(message interface{}) bool {
-                tunnelingRequest := driverModel.CastTunnelingRequest(message)
-                if tunnelingRequest == nil ||
-                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
-                    return false
-                }
-                lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-                if lDataCon == nil {
-                    return false
-                }
-                lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
-                if lDataFrameExt == nil {
-                    return false
-                }
-                // TODO: Check this too: lDataFrameExt.DestinationAddress
-                apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
-                if apduControlContainer == nil {
-                    return false
-                }
-                apduControlConnect := driverModel.CastApduControlConnect(apduControlContainer.ControlApdu)
-                return apduControlConnect != nil
-            },
-            func(message interface{}) error {
-                tunnelingRequest := driverModel.CastTunnelingRequest(message)
-                lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-                // If the error flag is set, there was an error connecting
-                if lDataCon.DataFrame.ErrorFlag {
-                    close(result)
-                }
-                lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
-                apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
-                apduControlConnect := driverModel.CastApduControlConnect(apduControlContainer.ControlApdu)
-
-                // Create a new connection object and save it in the map
-                deviceConnection := &KnxDeviceConnection{
-                    counter: 0,
-                    maxApdu: 0, // TODO: Initialize this with the default max APDU Size
-                }
-                m.DeviceConnections[targetAddress] = deviceConnection
-
-                result <- *apduControlConnect
-                return nil
-            },
-            m.defaultTtl)
-        if err != nil {
-            close(result)
-        }
-    }()
-
-    return result
+func (m *KnxNetIpConnection) sendDeviceConnectionRequest(targetAddress driverModel.KnxAddress) chan *driverModel.ApduControlConnect {
+	result := make(chan *driverModel.ApduControlConnect)
+
+	go func() {
+		// Send a connection request to the individual KNX device
+		deviceConnectionRequest := driverModel.NewTunnelingRequest(
+			driverModel.NewTunnelingRequestDataBlock(0, 0),
+			driverModel.NewLDataReq(0, nil,
+				driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+					driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
+					driverModel.NewApduControlContainer(driverModel.NewApduControlConnect(), 0, false, 0),
+					true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
+		err := m.sendRequest(
+			deviceConnectionRequest,
+			// The Gateway is now supposed to send an Ack to this request.
+			func(message interface{}) bool {
+				tunnelingRequest := driverModel.CastTunnelingRequest(message)
+				if tunnelingRequest == nil ||
+					tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+					return false
+				}
+				lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+				if lDataCon == nil {
+					return false
+				}
+				lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+				if lDataFrameExt == nil {
+					return false
+				}
+				// TODO: Check this too: lDataFrameExt.DestinationAddress
+				apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
+				if apduControlContainer == nil {
+					return false
+				}
+				apduControlConnect := driverModel.CastApduControlConnect(apduControlContainer.ControlApdu)
+				return apduControlConnect != nil
+			},
+			func(message interface{}) error {
+				tunnelingRequest := driverModel.CastTunnelingRequest(message)
+				lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+				// If the error flag is set, there was an error connecting
+				if lDataCon.DataFrame.ErrorFlag {
+					result <- nil
+					return nil
+				}
+				lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+				apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
+				apduControlConnect := driverModel.CastApduControlConnect(apduControlContainer.ControlApdu)
+
+				// Create a new connection object and save it in the map
+				deviceConnection := &KnxDeviceConnection{
+					counter: 0,
+					maxApdu: 0, // TODO: Initialize this with the default max APDU Size
+				}
+				m.DeviceConnections[targetAddress] = deviceConnection
+
+				result <- apduControlConnect
+				return nil
+			},
+			m.defaultTtl)
+		if err != nil {
+			close(result)
+		}
+	}()
+
+	return result
 }
 
 func (m *KnxNetIpConnection) sendDeviceDisconnectionRequest(targetAddress driverModel.KnxAddress) chan driverModel.ApduControlDisconnect {
-    result := make(chan driverModel.ApduControlDisconnect)
-
-    go func() {
-        // Send a connection request to the individual KNX device
-        deviceDisconnectionRequest := driverModel.NewTunnelingRequest(
-            driverModel.NewTunnelingRequestDataBlock(0, 0),
-            driverModel.NewLDataReq(0, nil,
-                driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
-                    driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
-                    driverModel.NewApduControlContainer(driverModel.NewApduControlDisconnect(), 0, false, 0),
-                    true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
-        err := m.sendRequest(
-            deviceDisconnectionRequest,
-            // The Gateway is now supposed to send an Ack to this request.
-            func(message interface{}) bool {
-                tunnelingRequest := driverModel.CastTunnelingRequest(message)
-                if tunnelingRequest == nil ||
-                    tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
-                    return false
-                }
-                lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-                if lDataCon == nil {
-                    return false
-                }
-                lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
-                if lDataFrameExt == nil {
-                    return false
-                }
-                // TODO: Check this too: lDataFrameExt.DestinationAddress
-                apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
-                if apduControlContainer == nil {
-                    return false
-                }
-                apduControlDisconnect := driverModel.CastApduControlDisconnect(apduControlContainer.ControlApdu)
-                return apduControlDisconnect != nil
-            },
-            func(message interface{}) error {
-                tunnelingRequest := driverModel.CastTunnelingRequest(message)
-                lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-                // If the error flag is set, there was an error connecting
-                if lDataCon.DataFrame.ErrorFlag {
-                    close(result)
-                }
-                lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
-                apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
-                apduControlDisconnect := driverModel.CastApduControlDisconnect(apduControlContainer.ControlApdu)
-
-                result <- *apduControlDisconnect
-                return nil
-            },
-            m.defaultTtl)
-        if err != nil {
-            close(result)
-        }
-    }()
-
-    return result
+	result := make(chan driverModel.ApduControlDisconnect)
+
+	go func() {
+		// Send a connection request to the individual KNX device
+		deviceDisconnectionRequest := driverModel.NewTunnelingRequest(
+			driverModel.NewTunnelingRequestDataBlock(0, 0),
+			driverModel.NewLDataReq(0, nil,
+				driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+					driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
+					driverModel.NewApduControlContainer(driverModel.NewApduControlDisconnect(), 0, false, 0),
+					true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
+		err := m.sendRequest(
+			deviceDisconnectionRequest,
+			// The Gateway is now supposed to send an Ack to this request.
+			func(message interface{}) bool {
+				tunnelingRequest := driverModel.CastTunnelingRequest(message)
+				if tunnelingRequest == nil ||
+					tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+					return false
+				}
+				lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+				if lDataCon == nil {
+					return false
+				}
+				lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+				if lDataFrameExt == nil {
+					return false
+				}
+				// TODO: Check this too: lDataFrameExt.DestinationAddress
+				apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
+				if apduControlContainer == nil {
+					return false
+				}
+				apduControlDisconnect := driverModel.CastApduControlDisconnect(apduControlContainer.ControlApdu)
+				return apduControlDisconnect != nil
+			},
+			func(message interface{}) error {
+				tunnelingRequest := driverModel.CastTunnelingRequest(message)
+				lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+				// If the error flag is set, there was an error connecting
+				if lDataCon.DataFrame.ErrorFlag {
+					close(result)
+				}
+				lDataFrameExt := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+				apduControlContainer := driverModel.CastApduControlContainer(lDataFrameExt.Apdu)
+				apduControlDisconnect := driverModel.CastApduControlDisconnect(apduControlContainer.ControlApdu)
+
+				result <- *apduControlDisconnect
+				return nil
+			},
+			m.defaultTtl)
+		if err != nil {
+			close(result)
+		}
+	}()
+
+	return result
 }
 
 func (m *KnxNetIpConnection) sendDeviceDeviceDescriptorReadRequest(targetAddress driverModel.KnxAddress) chan driverModel.ApduDataDeviceDescriptorResponse {
-    result := make(chan driverModel.ApduDataDeviceDescriptorResponse)
-
-    connection, ok := m.DeviceConnections[targetAddress]
-    if !ok {
-        close(result)
-        return result
-    }
-
-    // Next, read the device descriptor so we know how we have to communicate with the device.
-    counter := connection.counter
-    connection.counter++
-    deviceDescriptorReadRequest := driverModel.NewTunnelingRequest(
-        driverModel.NewTunnelingRequestDataBlock(
-            // This is actually set in the KnxNetIpConnection.SendMessage method
-            0,
-            // This is actually set in the KnxNetIpConnection.SendMessage method
-            0),
-        driverModel.NewLDataReq(0, nil,
-            driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
-                driverModel.NewKnxAddress(0, 0, 0),
-                KnxAddressToInt8Array(targetAddress),
-                driverModel.NewApduDataContainer(
-                    driverModel.NewApduDataDeviceDescriptorRead(0), 1, true, counter),
-                true, true, driverModel.CEMIPriority_LOW, false, false)))
-    err := m.sendRequest(
-        deviceDescriptorReadRequest,
-        func(message interface{}) bool {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            if tunnelingRequest == nil ||
-                tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
-                return false
-            }
-            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
-            if lDataInd == nil {
-                return false
-            }
-            dataFrame := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
-            if dataFrame == nil {
-                return false
-            }
-            // TODO: Check this too: dataFrame.SourceAddress
-            // TODO: Check this too: dataFrame.Apdu.Counter
-            dataContainer := driverModel.CastApduDataContainer(dataFrame.Apdu)
-            if dataContainer == nil {
-                return false
-            }
-            deviceDescriptorResponse := driverModel.CastApduDataDeviceDescriptorResponse(dataContainer.DataApdu)
-            if deviceDescriptorResponse == nil {
-                return false
-            }
-            return true
-        },
-        func(message interface{}) error {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
-            dataFrame := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
-            dataContainer := driverModel.CastApduDataContainer(dataFrame.Apdu)
-            deviceDescriptorResponse := driverModel.CastApduDataDeviceDescriptorResponse(dataContainer.DataApdu)
-
-            // Send an ACK and wait for it to be delivered
-            go func() {
-                ackResults := m.sendDeviceAck(targetAddress, dataFrame.Apdu.Counter)
-                select {
-                case ackResult := <-ackResults:
-                    if !ackResult {
-                        fmt.Printf("Error getting Ack")
-                    }
-                }
-
-                // Save the device-descriptor value
-                deviceDescriptor := uint16(deviceDescriptorResponse.Data[0])<<8 | uint16(deviceDescriptorResponse.Data[1])
-                connection.deviceDescriptor = deviceDescriptor
-
-                result <- *deviceDescriptorResponse
-            }()
-
-            return nil
-        },
-        time.Second*5)
-    if err != nil {
-        close(result)
-    }
-    return result
+	result := make(chan driverModel.ApduDataDeviceDescriptorResponse)
+
+	connection, ok := m.DeviceConnections[targetAddress]
+	if !ok {
+		close(result)
+		return result
+	}
+
+	// Next, read the device descriptor so we know how we have to communicate with the device.
+	counter := connection.counter
+	connection.counter++
+	deviceDescriptorReadRequest := driverModel.NewTunnelingRequest(
+		driverModel.NewTunnelingRequestDataBlock(
+			// This is actually set in the KnxNetIpConnection.SendMessage method
+			0,
+			// This is actually set in the KnxNetIpConnection.SendMessage method
+			0),
+		driverModel.NewLDataReq(0, nil,
+			driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+				driverModel.NewKnxAddress(0, 0, 0),
+				KnxAddressToInt8Array(targetAddress),
+				driverModel.NewApduDataContainer(
+					driverModel.NewApduDataDeviceDescriptorRead(0), 1, true, counter),
+				true, true, driverModel.CEMIPriority_LOW, false, false)))
+	err := m.sendRequest(
+		deviceDescriptorReadRequest,
+		func(message interface{}) bool {
+			tunnelingRequest := driverModel.CastTunnelingRequest(message)
+			if tunnelingRequest == nil ||
+				tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+				return false
+			}
+			lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+			if lDataInd == nil {
+				return false
+			}
+			dataFrame := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+			if dataFrame == nil {
+				return false
+			}
+			// TODO: Check this too: dataFrame.SourceAddress
+			// TODO: Check this too: dataFrame.Apdu.Counter
+			dataContainer := driverModel.CastApduDataContainer(dataFrame.Apdu)
+			if dataContainer == nil {
+				return false
+			}
+			deviceDescriptorResponse := driverModel.CastApduDataDeviceDescriptorResponse(dataContainer.DataApdu)
+			if deviceDescriptorResponse == nil {
+				return false
+			}
+			return true
+		},
+		func(message interface{}) error {
+			tunnelingRequest := driverModel.CastTunnelingRequest(message)
+			lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+			dataFrame := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+			dataContainer := driverModel.CastApduDataContainer(dataFrame.Apdu)
+			deviceDescriptorResponse := driverModel.CastApduDataDeviceDescriptorResponse(dataContainer.DataApdu)
+
+			// Send an ACK and wait for it to be delivered
+			go func() {
+				ackResults := m.sendDeviceAck(targetAddress, dataFrame.Apdu.Counter)
+				select {
+				case ackResult := <-ackResults:
+					if !ackResult {
+						fmt.Printf("Error getting Ack")
+					}
+				}
+
+				// Save the device-descriptor value
+				deviceDescriptor := uint16(deviceDescriptorResponse.Data[0])<<8 | uint16(deviceDescriptorResponse.Data[1])
+				connection.deviceDescriptor = deviceDescriptor
+
+				result <- *deviceDescriptorResponse
+			}()
+
+			return nil
+		},
+		time.Second*5)
+	if err != nil {
+		close(result)
+	}
+	return result
 }
 
 func (m *KnxNetIpConnection) sendDevicePropertyReadRequest(targetAddress driverModel.KnxAddress, objectId uint8, propertyId uint8) chan KnxReadResult {
-    result := make(chan KnxReadResult)
-
-    connection, ok := m.DeviceConnections[targetAddress]
-    if !ok {
-        close(result)
-        result <- KnxReadResult{
-            returnCode: apiModel.PlcResponseCode_INTERNAL_ERROR,
-        }
-    }
-
-    // Next, read the device descriptor so we know how we have to communicate with the device.
-    counter := connection.counter
-    connection.counter++
-
-    // Send the property read request and wait for a confirmation that this property is readable.
-    propertyReadRequest := driverModel.NewTunnelingRequest(
-        driverModel.NewTunnelingRequestDataBlock(0, 0),
-        driverModel.NewLDataReq(0, nil,
-            driverModel.NewLDataFrameDataExt(false, 6, 0,
-                driverModel.NewKnxAddress(0, 0, 0),
-                KnxAddressToInt8Array(targetAddress),
-                driverModel.NewApduDataContainer(
-                    driverModel.NewApduDataOther(
-                        // TODO: The counter should be incremented per KNX individual address
-                        driverModel.NewApduDataExtPropertyValueRead(objectId, propertyId, 1, 1)),
-                    5, true, counter),
-                true, true, driverModel.CEMIPriority_LOW, false, false)))
-    err := m.sendRequest(
-        propertyReadRequest,
-        func(message interface{}) bool {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            if tunnelingRequest == nil ||
-                tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
-                return false
-            }
-            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
-            if lDataInd == nil {
-                return false
-            }
-            var apdu *driverModel.Apdu
-            dataFrameExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
-            if dataFrameExt != nil {
-                apdu = dataFrameExt.Apdu
-            } else {
-                dataFrame := driverModel.CastLDataFrameData(lDataInd.DataFrame)
-                if dataFrame != nil {
-                    apdu = dataFrame.Apdu
-                }
-            }
-            if apdu == nil {
-                return false
-            }
-            // TODO: Check this too: dataFrame.SourceAddress
-            // TODO: Check this too: dataFrame.Apdu.Counter
-            dataContainer := driverModel.CastApduDataContainer(apdu)
-            if dataContainer == nil {
-                return false
-            }
-            dataApduOther := driverModel.CastApduDataOther(dataContainer.DataApdu)
-            if dataApduOther == nil {
-                return false
-            }
-            propertyValueResponse := driverModel.CastApduDataExtPropertyValueResponse(dataApduOther.ExtendedApdu)
-            if propertyValueResponse == nil {
-                return false
-            }
-            return propertyValueResponse.ObjectIndex == objectId && propertyValueResponse.PropertyId == propertyId
-        },
-        func(message interface{}) error {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
-            var apdu *driverModel.Apdu
-            dataFrameExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
-            if dataFrameExt != nil {
-                apdu = dataFrameExt.Apdu
-            } else {
-                dataFrame := driverModel.CastLDataFrameData(lDataInd.DataFrame)
-                if dataFrame != nil {
-                    apdu = dataFrame.Apdu
-                }
-            }
-            dataContainer := driverModel.CastApduDataContainer(apdu)
-            dataApduOther := driverModel.CastApduDataOther(dataContainer.DataApdu)
-            propertyValueResponse := driverModel.CastApduDataExtPropertyValueResponse(dataApduOther.ExtendedApdu)
-
-            // Send an ACK and wait for it to be delivered
-            go func() {
-                ackResults := m.sendDeviceAck(targetAddress, apdu.Counter)
-                select {
-                case ackResult := <-ackResults:
-                    if !ackResult {
-                        fmt.Printf("Error getting Ack")
-                    }
-                }
-
-                // If the count is 0, then this property doesn't exist or the user has no permission to read it.
-                if propertyValueResponse.Count == 0 {
-                    result <- KnxReadResult{
-                        returnCode: apiModel.PlcResponseCode_NOT_FOUND,
-                    }
-                } else {
-                    // Find out the type of the property
-                    var objectType *driverModel.KnxInterfaceObjectType
-                    for curObjectType := driverModel.KnxInterfaceObjectType_OT_UNKNOWN; curObjectType <= driverModel.KnxInterfaceObjectType_OT_SUNBLIND_SENSOR_BASIC; curObjectType++ {
-                        if curObjectType.Code() == strconv.Itoa(int(objectId)) {
-                            objectType = &curObjectType
-                            break
-                        }
-                    }
-                    property := driverModel.KnxInterfaceObjectProperty_PID_UNKNOWN
-                    if objectType != nil {
-                        for curProperty := driverModel.KnxInterfaceObjectProperty_PID_UNKNOWN; curProperty <= driverModel.KnxInterfaceObjectProperty_PID_SUNBLIND_SENSOR_BASIC_ENABLE_TOGGLE_MODE; curProperty++ {
-                            if curProperty.PropertyId() == propertyId {
-                                if curProperty.ObjectType() == driverModel.KnxInterfaceObjectType_OT_GENERAL || curProperty.ObjectType() == *objectType {
-                                    property = curProperty
-                                    break
-                                }
-                            }
-                        }
-                    }
-
-                    // Parse the data according to the property type information
-                    propertyDataType := property.PropertyDataType()
-                    dataLength := uint8(len(propertyValueResponse.Data))
-                    data := propertyValueResponse.Data
-                    rb := utils.NewReadBuffer(data)
-                    plcValue, err := driverModel.KnxPropertyParse(rb, propertyDataType, dataLength)
-
-                    // Return the result
-                    if err != nil {
-                        result <- KnxReadResult{
-                            returnCode: apiModel.PlcResponseCode_INTERNAL_ERROR,
-                            value:      nil,
-                        }
-                    } else {
-                        result <- KnxReadResult{
-                            returnCode: apiModel.PlcResponseCode_OK,
-                            value:      &plcValue,
-                        }
-                    }
-                }
-
-            }()
-            return nil
-        },
-        time.Second*5)
-
-    if err != nil {
-        close(result)
-    }
-    return result
+	result := make(chan KnxReadResult)
+
+	connection, ok := m.DeviceConnections[targetAddress]
+	if !ok {
+		close(result)
+		result <- KnxReadResult{
+			returnCode: apiModel.PlcResponseCode_INTERNAL_ERROR,
+		}
+	}
+
+	// Next, read the device descriptor so we know how we have to communicate with the device.
+	counter := connection.counter
+	connection.counter++
+
+	// Send the property read request and wait for a confirmation that this property is readable.
+	propertyReadRequest := driverModel.NewTunnelingRequest(
+		driverModel.NewTunnelingRequestDataBlock(0, 0),
+		driverModel.NewLDataReq(0, nil,
+			driverModel.NewLDataFrameDataExt(false, 6, 0,
+				driverModel.NewKnxAddress(0, 0, 0),
+				KnxAddressToInt8Array(targetAddress),
+				driverModel.NewApduDataContainer(
+					driverModel.NewApduDataOther(
+						// TODO: The counter should be incremented per KNX individual address
+						driverModel.NewApduDataExtPropertyValueRead(objectId, propertyId, 1, 1)),
+					5, true, counter),
+				true, true, driverModel.CEMIPriority_LOW, false, false)))
+	err := m.sendRequest(
+		propertyReadRequest,
+		func(message interface{}) bool {
+			tunnelingRequest := driverModel.CastTunnelingRequest(message)
+			if tunnelingRequest == nil ||
+				tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+				return false
+			}
+			lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+			if lDataInd == nil {
+				return false
+			}
+			var apdu *driverModel.Apdu
+			dataFrameExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+			if dataFrameExt != nil {
+				apdu = dataFrameExt.Apdu
+			} else {
+				dataFrame := driverModel.CastLDataFrameData(lDataInd.DataFrame)
+				if dataFrame != nil {
+					apdu = dataFrame.Apdu
+				}
+			}
+			if apdu == nil {
+				return false
+			}
+			// TODO: Check this too: dataFrame.SourceAddress
+			// TODO: Check this too: dataFrame.Apdu.Counter
+			dataContainer := driverModel.CastApduDataContainer(apdu)
+			if dataContainer == nil {
+				return false
+			}
+			dataApduOther := driverModel.CastApduDataOther(dataContainer.DataApdu)
+			if dataApduOther == nil {
+				return false
+			}
+			propertyValueResponse := driverModel.CastApduDataExtPropertyValueResponse(dataApduOther.ExtendedApdu)
+			if propertyValueResponse == nil {
+				return false
+			}
+			return propertyValueResponse.ObjectIndex == objectId && propertyValueResponse.PropertyId == propertyId
+		},
+		func(message interface{}) error {
+			tunnelingRequest := driverModel.CastTunnelingRequest(message)
+			lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi)
+			var apdu *driverModel.Apdu
+			dataFrameExt := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+			if dataFrameExt != nil {
+				apdu = dataFrameExt.Apdu
+			} else {
+				dataFrame := driverModel.CastLDataFrameData(lDataInd.DataFrame)
+				if dataFrame != nil {
+					apdu = dataFrame.Apdu
+				}
+			}
+			dataContainer := driverModel.CastApduDataContainer(apdu)
+			dataApduOther := driverModel.CastApduDataOther(dataContainer.DataApdu)
+			propertyValueResponse := driverModel.CastApduDataExtPropertyValueResponse(dataApduOther.ExtendedApdu)
+
+			// Send an ACK and wait for it to be delivered
+			go func() {
+				ackResults := m.sendDeviceAck(targetAddress, apdu.Counter)
+				select {
+				case ackResult := <-ackResults:
+					if !ackResult {
+						fmt.Printf("Error getting Ack")
+					}
+				}
+
+				// If the count is 0, then this property doesn't exist or the user has no permission to read it.
+				if propertyValueResponse.Count == 0 {
+					result <- KnxReadResult{
+						returnCode: apiModel.PlcResponseCode_NOT_FOUND,
+					}
+				} else {
+					// Find out the type of the property
+					var objectType *driverModel.KnxInterfaceObjectType
+					for curObjectType := driverModel.KnxInterfaceObjectType_OT_UNKNOWN; curObjectType <= driverModel.KnxInterfaceObjectType_OT_SUNBLIND_SENSOR_BASIC; curObjectType++ {
+						if curObjectType.Code() == strconv.Itoa(int(objectId)) {
+							objectType = &curObjectType
+							break
+						}
+					}
+					property := driverModel.KnxInterfaceObjectProperty_PID_UNKNOWN
+					if objectType != nil {
+						for curProperty := driverModel.KnxInterfaceObjectProperty_PID_UNKNOWN; curProperty <= driverModel.KnxInterfaceObjectProperty_PID_SUNBLIND_SENSOR_BASIC_ENABLE_TOGGLE_MODE; curProperty++ {
+							if curProperty.PropertyId() == propertyId {
+								if curProperty.ObjectType() == driverModel.KnxInterfaceObjectType_OT_GENERAL || curProperty.ObjectType() == *objectType {
+									property = curProperty
+									break
+								}
+							}
+						}
+					}
+
+					// Parse the data according to the property type information
+					propertyDataType := property.PropertyDataType()
+					dataLength := uint8(len(propertyValueResponse.Data))
+					data := propertyValueResponse.Data
+					rb := utils.NewReadBuffer(data)
+					plcValue, err := driverModel.KnxPropertyParse(rb, propertyDataType, dataLength)
+
+					// Return the result
+					if err != nil {
+						result <- KnxReadResult{
+							returnCode: apiModel.PlcResponseCode_INTERNAL_ERROR,
+							value:      nil,
+						}
+					} else {
+						result <- KnxReadResult{
+							returnCode: apiModel.PlcResponseCode_OK,
+							value:      &plcValue,
+						}
+					}
+				}
+
+			}()
+			return nil
+		},
+		time.Second*5)
+
+	if err != nil {
+		close(result)
+	}
+	return result
 }
 
 func (m *KnxNetIpConnection) sendDeviceAck(targetAddress driverModel.KnxAddress, counter uint8) chan bool {
-    result := make(chan bool)
-
-    ack := driverModel.NewTunnelingRequest(
-        driverModel.NewTunnelingRequestDataBlock(0, 0),
-        driverModel.NewLDataReq(0, nil,
-            driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
-                driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
-                driverModel.NewApduControlContainer(driverModel.NewApduControlAck(), 0, true, counter),
-                true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
-    err := m.sendRequest(
-        ack,
-        func(message interface{}) bool {
-            tunnelingRequest := driverModel.CastTunnelingRequest(message)
-            if tunnelingRequest == nil ||
-                tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
-                return false
-            }
-            lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
-            if lDataCon == nil {
-                return false
-            }
-            dataFrame := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
-            if dataFrame == nil {
-                return false
-            }
-            // TODO: Check this too: dataFrame.SourceAddress (This should match the targetAddress)
-            // TODO: Check this too: dataFrame.DestinationAddress (This should match our KNX Address)
-            // TODO: Check this too: dataFrame.Apdu.Counter
-            controlContainer := driverModel.CastApduControlContainer(dataFrame.Apdu)
-            if controlContainer == nil {
-                return false
-            }
-            dataApduAck := driverModel.CastApduControlAck(controlContainer.ControlApdu)
-            if dataApduAck == nil {
-                return false
-            }
-            return true
-        },
-        func(message interface{}) error {
-            result <- true
-            return nil
-        },
-        time.Second*5)
-
-    if err != nil {
-        result <- false
-    }
-
-    return result
+	result := make(chan bool)
+
+	ack := driverModel.NewTunnelingRequest(
+		driverModel.NewTunnelingRequestDataBlock(0, 0),
+		driverModel.NewLDataReq(0, nil,
+			driverModel.NewLDataFrameDataExt(false, 6, uint8(0),
+				driverModel.NewKnxAddress(0, 0, 0), KnxAddressToInt8Array(targetAddress),
+				driverModel.NewApduControlContainer(driverModel.NewApduControlAck(), 0, true, counter),
+				true, true, driverModel.CEMIPriority_SYSTEM, false, false)))
+	err := m.sendRequest(
+		ack,
+		func(message interface{}) bool {
+			tunnelingRequest := driverModel.CastTunnelingRequest(message)
+			if tunnelingRequest == nil ||
+				tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId != m.CommunicationChannelId {
+				return false
+			}
+			lDataCon := driverModel.CastLDataCon(tunnelingRequest.Cemi)
+			if lDataCon == nil {
+				return false
+			}
+			dataFrame := driverModel.CastLDataFrameDataExt(lDataCon.DataFrame)
+			if dataFrame == nil {
+				return false
+			}
+			// TODO: Check this too: dataFrame.SourceAddress (This should match the targetAddress)
+			// TODO: Check this too: dataFrame.DestinationAddress (This should match our KNX Address)
+			// TODO: Check this too: dataFrame.Apdu.Counter
+			controlContainer := driverModel.CastApduControlContainer(dataFrame.Apdu)
+			if controlContainer == nil {
+				return false
+			}
+			dataApduAck := driverModel.CastApduControlAck(controlContainer.ControlApdu)
+			if dataApduAck == nil {
+				return false
+			}
+			return true
+		},
+		func(message interface{}) error {
+			result <- true
+			return nil
+		},
+		time.Second*5)
+
+	if err != nil {
+		result <- false
+	}
+
+	return result
 }
 
 func (m *KnxNetIpConnection) sendRequest(request *driverModel.KnxNetIpMessage, acceptsMessage spi.AcceptsMessage, handleMessage spi.HandleMessage, ttl time.Duration) error {
-    // If this is a tunneling request, we need to update the communicationChannelId and assign a sequenceCounter
-    tunnelingRequest := driverModel.CastTunnelingRequest(request)
-    if tunnelingRequest != nil {
-        tunnelingRequest.TunnelingRequestDataBlock.CommunicationChannelId = m.CommunicationChannelId
-        tunnelingRequest.TunnelingRequestDataBlock.SequenceCounter = m.getNewSequenceCounter()
-    }
-    return m.messageCodec.SendRequest(request, acceptsMessage, handleMessage, ttl)
+	// 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.SendRequest(request, acceptsMessage, handleMessage, ttl)
 }
 
 func (m *KnxNetIpConnection) getLocalAddress() (*net.UDPAddr, error) {
-    transportInstanceExposer, ok := m.messageCodec.(spi.TransportInstanceExposer)
-    if !ok {
-        return nil, errors.New("used transport, is not a TransportInstanceExposer")
-    }
-
-    // Prepare a SearchReq
-    udpTransportInstance, ok := transportInstanceExposer.GetTransportInstance().(*udp.UdpTransportInstance)
-    if !ok {
-        return nil, errors.New("used transport, is not a UdpTransportInstance")
-    }
-
-    return udpTransportInstance.LocalAddress, nil
+	transportInstanceExposer, ok := m.messageCodec.(spi.TransportInstanceExposer)
+	if !ok {
+		return nil, errors.New("used transport, is not a TransportInstanceExposer")
+	}
+
+	// Prepare a SearchReq
+	udpTransportInstance, ok := transportInstanceExposer.GetTransportInstance().(*udp.UdpTransportInstance)
+	if !ok {
+		return nil, errors.New("used transport, is not a UdpTransportInstance")
+	}
+
+	return udpTransportInstance.LocalAddress, nil
 }
 
 func (m *KnxNetIpConnection) interceptIncomingMessage(interface{}) {
-    if m.connectionStateTimer != nil {
-        // Reset the timer for sending the ConnectionStateRequest
-        m.connectionStateTimer.Reset(60 * time.Second)
-    }
+	if m.connectionStateTimer != nil {
+		// Reset the timer for sending the ConnectionStateRequest
+		m.connectionStateTimer.Reset(60 * time.Second)
+	}
 }
 
 func (m *KnxNetIpConnection) getNewSequenceCounter() uint8 {
-    sequenceCounter := atomic.AddInt32(&m.SequenceCounter, 1)
-    if sequenceCounter >= math.MaxUint8 {
-        atomic.StoreInt32(&m.SequenceCounter, -1)
-        sequenceCounter = -1
-    }
-    return uint8(sequenceCounter)
+	sequenceCounter := atomic.AddInt32(&m.SequenceCounter, 1)
+	if sequenceCounter >= math.MaxUint8 {
+		atomic.StoreInt32(&m.SequenceCounter, -1)
+		sequenceCounter = -1
+	}
+	return uint8(sequenceCounter)
 }
 
 func (m *KnxNetIpConnection) castIpToKnxAddress(ip net.IP) *driverModel.IPAddress {
-    return driverModel.NewIPAddress(utils.ByteArrayToInt8Array(ip)[len(ip)-4:])
+	return driverModel.NewIPAddress(utils.ByteArrayToInt8Array(ip)[len(ip)-4:])
 }
 
 func (m *KnxNetIpConnection) handleIncomingTunnelingRequest(tunnelingRequest *driverModel.TunnelingRequest) {
-    go func() {
-        lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi.Child)
-        if lDataInd != nil {
-            var destinationAddress []int8
-            var apdu *driverModel.Apdu
-            switch lDataInd.DataFrame.Child.(type) {
-            case *driverModel.LDataFrameData:
-                dataFrame := driverModel.CastLDataFrameData(lDataInd.DataFrame)
-                destinationAddress = dataFrame.DestinationAddress
-                apdu = dataFrame.Apdu
-            case *driverModel.LDataFrameDataExt:
-                dataFrame := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
-                destinationAddress = dataFrame.DestinationAddress
-                apdu = dataFrame.Apdu
-            }
-            container := driverModel.CastApduDataContainer(apdu)
-            if container == nil {
-                return
-            }
-            groupValueWrite := driverModel.CastApduDataGroupValueWrite(container.DataApdu)
-            if groupValueWrite == nil {
-                return
-            }
-            if destinationAddress != nil {
-                addressData := uint16(destinationAddress[0])<<8 | (uint16(destinationAddress[1]) & 0xFF)
-                m.valueCacheMutex.RLock()
-                val, ok := m.valueCache[addressData]
-                m.valueCacheMutex.RUnlock()
-                changed := false
-
-                var payload []int8
-                payload = append(payload, groupValueWrite.DataFirstByte)
-                payload = append(payload, groupValueWrite.Data...)
-                if !ok || !m.sliceEqual(val, payload) {
-                    m.valueCacheMutex.Lock()
-                    m.valueCache[addressData] = payload
-                    m.valueCacheMutex.Unlock()
-                    // If this is a new value, we have to also provide the 3 different types of addresses.
-                    if !ok {
-                        arb := utils.NewReadBuffer(utils.Int8ArrayToUint8Array(destinationAddress))
-                        if address, err2 := driverModel.KnxGroupAddressParse(arb, 3); err2 == nil {
-                            m.leve3AddressCache[addressData] = driverModel.CastKnxGroupAddress3Level(address)
-                        } else {
-                            fmt.Printf("Error parsing Group Address %s", err2.Error())
-                        }
-                        arb.Reset()
-                        if address, err2 := driverModel.KnxGroupAddressParse(arb, 2); err2 == nil {
-                            m.leve2AddressCache[addressData] = driverModel.CastKnxGroupAddress2Level(address)
-                        }
-                        arb.Reset()
-                        if address, err2 := driverModel.KnxGroupAddressParse(arb, 1); err2 == nil {
-                            m.leve1AddressCache[addressData] = driverModel.CastKnxGroupAddressFreeLevel(address)
-                        }
-                    }
-                    changed = true
-                }
-                if m.subscribers != nil {
-                    for _, subscriber := range m.subscribers {
-                        subscriber.handleValueChange(lDataInd.DataFrame, changed)
-                    }
-                }
-            }
-        }
-    }()
+	go func() {
+		lDataInd := driverModel.CastLDataInd(tunnelingRequest.Cemi.Child)
+		if lDataInd != nil {
+			var destinationAddress []int8
+			var apdu *driverModel.Apdu
+			switch lDataInd.DataFrame.Child.(type) {
+			case *driverModel.LDataFrameData:
+				dataFrame := driverModel.CastLDataFrameData(lDataInd.DataFrame)
+				destinationAddress = dataFrame.DestinationAddress
+				apdu = dataFrame.Apdu
+			case *driverModel.LDataFrameDataExt:
+				dataFrame := driverModel.CastLDataFrameDataExt(lDataInd.DataFrame)
+				destinationAddress = dataFrame.DestinationAddress
+				apdu = dataFrame.Apdu
+			}
+			container := driverModel.CastApduDataContainer(apdu)
+			if container == nil {
+				return
+			}
+			groupValueWrite := driverModel.CastApduDataGroupValueWrite(container.DataApdu)
+			if groupValueWrite == nil {
+				return
+			}
+			if destinationAddress != nil {
+				addressData := uint16(destinationAddress[0])<<8 | (uint16(destinationAddress[1]) & 0xFF)
+				m.valueCacheMutex.RLock()
+				val, ok := m.valueCache[addressData]
+				m.valueCacheMutex.RUnlock()
+				changed := false
+
+				var payload []int8
+				payload = append(payload, groupValueWrite.DataFirstByte)
+				payload = append(payload, groupValueWrite.Data...)
+				if !ok || !m.sliceEqual(val, payload) {
+					m.valueCacheMutex.Lock()
+					m.valueCache[addressData] = payload
+					m.valueCacheMutex.Unlock()
+					// If this is a new value, we have to also provide the 3 different types of addresses.
+					if !ok {
+						arb := utils.NewReadBuffer(utils.Int8ArrayToUint8Array(destinationAddress))
+						if address, err2 := driverModel.KnxGroupAddressParse(arb, 3); err2 == nil {
+							m.leve3AddressCache[addressData] = driverModel.CastKnxGroupAddress3Level(address)
+						} else {
+							fmt.Printf("Error parsing Group Address %s", err2.Error())
+						}
+						arb.Reset()
+						if address, err2 := driverModel.KnxGroupAddressParse(arb, 2); err2 == nil {
+							m.leve2AddressCache[addressData] = driverModel.CastKnxGroupAddress2Level(address)
+						}
+						arb.Reset()
+						if address, err2 := driverModel.KnxGroupAddressParse(arb, 1); err2 == nil {
+							m.leve1AddressCache[addressData] = driverModel.CastKnxGroupAddressFreeLevel(address)
+						}
+					}
+					changed = true
+				}
+				if m.subscribers != nil {
+					for _, subscriber := range m.subscribers {
+						subscriber.handleValueChange(lDataInd.DataFrame, changed)
+					}
+				}
+			}
+		}
+	}()
 }
 
 func (m *KnxNetIpConnection) getGroupAddressNumLevels() uint8 {
-    if val, ok := m.options["group-address-num-levels"]; ok {
-        groupAddressNumLevels, err := strconv.Atoi(val[0])
-        if err == nil {
-            return uint8(groupAddressNumLevels)
-        }
-    }
-    return 3
+	if val, ok := m.options["group-address-num-levels"]; ok {
+		groupAddressNumLevels, err := strconv.Atoi(val[0])
+		if err == nil {
+			return uint8(groupAddressNumLevels)
+		}
+	}
+	return 3
 }
 
 func (m *KnxNetIpConnection) addSubscriber(subscriber *KnxNetIpSubscriber) {
-    for _, sub := range m.subscribers {
-        if sub == subscriber {
-            return
-        }
-    }
-    m.subscribers = append(m.subscribers, subscriber)
+	for _, sub := range m.subscribers {
+		if sub == subscriber {
+			return
+		}
+	}
+	m.subscribers = append(m.subscribers, subscriber)
 }
 
 func (m *KnxNetIpConnection) removeSubscriber(subscriber *KnxNetIpSubscriber) {
-    for i, sub := range m.subscribers {
-        if sub == subscriber {
-            m.subscribers = append(m.subscribers[:i], m.subscribers[i+1:]...)
-        }
-    }
+	for i, sub := range m.subscribers {
+		if sub == subscriber {
+			m.subscribers = append(m.subscribers[:i], m.subscribers[i+1:]...)
+		}
+	}
 }
 
 func (m *KnxNetIpConnection) sliceEqual(a, b []int8) bool {
-    if len(a) != len(b) {
-        return false
-    }
-    for i, v := range a {
-        if v != b[i] {
-            return false
-        }
-    }
-    return true
+	if len(a) != len(b) {
+		return false
+	}
+	for i, v := range a {
+		if v != b[i] {
+			return false
+		}
+	}
+	return true
 }
 
 func KnxAddressToString(knxAddress *driverModel.KnxAddress) string {
-    return strconv.Itoa(int(knxAddress.MainGroup)) + "." + strconv.Itoa(int(knxAddress.MiddleGroup)) + "." + strconv.Itoa(int(knxAddress.SubGroup))
+	return strconv.Itoa(int(knxAddress.MainGroup)) + "." + strconv.Itoa(int(knxAddress.MiddleGroup)) + "." + strconv.Itoa(int(knxAddress.SubGroup))
 }