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