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