blob: 8898ffcae19342e75413d0176a1f7add78857e3b [file] [log] [blame]
/*
* 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
*
* https://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 knxnetip
import (
"context"
"encoding/hex"
"fmt"
"github.com/apache/plc4x/plc4go/spi/options"
"github.com/rs/zerolog"
"strconv"
"strings"
"time"
_default "github.com/apache/plc4x/plc4go/spi/default"
apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
"github.com/apache/plc4x/plc4go/pkg/api/values"
driverModel "github.com/apache/plc4x/plc4go/protocols/knxnetip/readwrite/model"
"github.com/apache/plc4x/plc4go/spi"
spiModel "github.com/apache/plc4x/plc4go/spi/model"
"github.com/apache/plc4x/plc4go/spi/utils"
"github.com/pkg/errors"
)
type Browser struct {
_default.DefaultBrowser
connection *Connection
messageCodec spi.MessageCodec
sequenceCounter uint8
log zerolog.Logger
}
func NewBrowser(connection *Connection, messageCodec spi.MessageCodec, _options ...options.WithOption) *Browser {
browser := Browser{
connection: connection,
messageCodec: messageCodec,
sequenceCounter: 0,
log: options.ExtractCustomLogger(_options...),
}
browser.DefaultBrowser = _default.NewDefaultBrowser(browser, _options...)
return &browser
}
func (m Browser) BrowseQuery(ctx context.Context, interceptor func(result apiModel.PlcBrowseItem) bool, _ string, query apiModel.PlcQuery) (apiModel.PlcResponseCode, []apiModel.PlcBrowseItem) {
switch query.(type) {
case DeviceQuery:
queryResults, err := m.executeDeviceQuery(ctx, query.(DeviceQuery), interceptor)
if err != nil {
m.log.Warn().Err(err).Msg("Error executing device query")
return apiModel.PlcResponseCode_INTERNAL_ERROR, nil
} else {
return apiModel.PlcResponseCode_OK, queryResults
}
case CommunicationObjectQuery:
queryResults, err := m.executeCommunicationObjectQuery(ctx, query.(CommunicationObjectQuery))
if err != nil {
m.log.Warn().Err(err).Msg("Error executing device query")
return apiModel.PlcResponseCode_INTERNAL_ERROR, nil
} else {
return apiModel.PlcResponseCode_OK, queryResults
}
default:
return apiModel.PlcResponseCode_INTERNAL_ERROR, nil
}
}
func (m Browser) executeDeviceQuery(ctx context.Context, query DeviceQuery, interceptor func(result apiModel.PlcBrowseItem) bool) ([]apiModel.PlcBrowseItem, error) {
// Create a list of address strings, which doesn't contain any ranges, lists or wildcards
knxAddresses, err := m.calculateAddresses(query)
if err != nil {
return nil, err
}
if len(knxAddresses) == 0 {
return nil, errors.New("query resulted in not a single valid address")
}
var queryResults []apiModel.PlcBrowseItem
// Parse each of these expanded addresses and handle them accordingly.
for _, knxAddress := range knxAddresses {
// Send a connection request to the device
connectTtlTimer := time.NewTimer(m.connection.defaultTtl)
deviceConnections := m.connection.DeviceConnect(ctx, knxAddress)
select {
case deviceConnection := <-deviceConnections:
if !connectTtlTimer.Stop() {
<-connectTtlTimer.C
}
// If the request returned a connection, process it,
// otherwise just ignore it.
if deviceConnection.connection != nil {
queryResult := spiModel.NewDefaultPlcBrowseItem(
NewDeviceQuery(
strconv.Itoa(int(knxAddress.GetMainGroup())),
strconv.Itoa(int(knxAddress.GetMiddleGroup())),
strconv.Itoa(int(knxAddress.GetSubGroup())),
),
"",
"",
false,
false,
false,
nil,
nil,
)
// Pass it to the callback
add := true
if interceptor != nil {
add = interceptor(queryResult)
}
// If the interceptor opted for adding it to the result, do so
if add {
queryResults = append(queryResults, queryResult)
}
disconnectTtlTimer := time.NewTimer(m.connection.defaultTtl * 10)
deviceDisconnections := m.connection.DeviceDisconnect(ctx, knxAddress)
select {
case _ = <-deviceDisconnections:
if !disconnectTtlTimer.Stop() {
<-disconnectTtlTimer.C
}
case <-disconnectTtlTimer.C:
disconnectTtlTimer.Stop()
// Just ignore this case ...
}
}
case <-connectTtlTimer.C:
connectTtlTimer.Stop()
// In this case the remote was just not responding.
}
// 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)
}
return queryResults, nil
}
func (m Browser) executeCommunicationObjectQuery(ctx context.Context, query CommunicationObjectQuery) ([]apiModel.PlcBrowseItem, error) {
var results []apiModel.PlcBrowseItem
knxAddress := query.toKnxAddress()
knxAddressString := KnxAddressToString(knxAddress)
// If we have a building Key, try that to login in order to access protected
if m.connection.buildingKey != nil {
arr := m.connection.DeviceAuthenticate(ctx, knxAddress, m.connection.buildingKey)
<-arr
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Group Address Table reading
/////////////////////////////////////////////////////////////////////////////////////////////////////
// First, request the starting address of the group address table
readRequestBuilder := m.connection.ReadRequestBuilder()
readRequestBuilder.AddTagAddress("groupAddressTableAddress", knxAddressString+"#1/7")
readRequest, err := readRequestBuilder.Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr := readRequest.Execute()
readResult := <-rrr
if readResult.GetErr() != nil {
return nil, errors.Wrap(readResult.GetErr(), "error reading the group address table starting address:")
}
if readResult.GetResponse().GetResponseCode("groupAddressTableAddress") != apiModel.PlcResponseCode_OK {
return nil, errors.Errorf("error reading group address table starting address: %s",
readResult.GetResponse().GetResponseCode("groupAddressTableAddress").GetName())
}
groupAddressTableStartAddress := readResult.GetResponse().GetValue("groupAddressTableAddress").GetUint32()
// Then read one byte at the given location.
// This will return the number of entries in the group address table (each 2 bytes)
readRequestBuilder = m.connection.ReadRequestBuilder()
// Depending on the type of device, query an USINT (1 byte) or UINT (2 bytes)
// TODO: Do this correctly depending on the device connection device-descriptor
if m.connection.DeviceConnections[knxAddress].deviceDescriptor == uint16(0x07B0) /* SystemB */ {
readRequestBuilder.AddTagAddress("numberOfAddressTableEntries",
fmt.Sprintf("%s#%X:UINT", knxAddressString, groupAddressTableStartAddress))
} else {
readRequestBuilder.AddTagAddress("numberOfAddressTableEntries",
fmt.Sprintf("%s#%X:USINT", knxAddressString, groupAddressTableStartAddress))
}
readRequest, err = readRequestBuilder.Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr = readRequest.Execute()
readResult = <-rrr
if readResult.GetErr() != nil {
return nil, errors.Wrap(readResult.GetErr(), "error reading the number of group address table entries")
}
if readResult.GetResponse().GetResponseCode("numberOfAddressTableEntries") != apiModel.PlcResponseCode_OK {
return nil, errors.Errorf("error reading the number of group address table entries: %s",
readResult.GetResponse().GetResponseCode("numberOfAddressTableEntries").GetName())
}
numGroupAddresses := readResult.GetResponse().GetValue("numberOfAddressTableEntries").GetUint16()
if m.connection.DeviceConnections[knxAddress].deviceDescriptor == uint16(0x07B0) /* SystemB */ {
groupAddressTableStartAddress += 2
} else {
groupAddressTableStartAddress += 3
numGroupAddresses--
}
// Abort, if there aren't any addresses to read.
if numGroupAddresses == 0 {
return results, nil
}
// Read the data in the group address table
readRequest, err = m.connection.ReadRequestBuilder().
AddTagAddress("groupAddressTable",
fmt.Sprintf("%s#%X:UINT[%d]", knxAddressString, groupAddressTableStartAddress, numGroupAddresses)).
Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr = readRequest.Execute()
readResult = <-rrr
if readResult.GetErr() != nil {
return nil, errors.Wrap(readResult.GetErr(), "error reading the group address table content")
}
if (readResult.GetResponse() == nil) ||
(readResult.GetResponse().GetResponseCode("groupAddressTable") != apiModel.PlcResponseCode_OK) {
return nil, errors.Errorf("error reading the group address table content: %s",
readResult.GetResponse().GetResponseCode("groupAddressTable").GetName())
}
var knxGroupAddresses []driverModel.KnxGroupAddress
if readResult.GetResponse().GetValue("groupAddressTable").IsList() {
for _, groupAddress := range readResult.GetResponse().GetValue("groupAddressTable").GetList() {
groupAddress := Uint16ToKnxGroupAddress(groupAddress.GetUint16(), 3)
knxGroupAddresses = append(knxGroupAddresses, groupAddress)
}
} else {
groupAddress := Uint16ToKnxGroupAddress(readResult.GetResponse().GetValue("groupAddressTable").GetUint16(), 3)
knxGroupAddresses = append(knxGroupAddresses, groupAddress)
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Group Address Association Table reading
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Now we read the group address association table address
readRequestBuilder = m.connection.ReadRequestBuilder()
readRequestBuilder.AddTagAddress("groupAddressAssociationTableAddress",
fmt.Sprintf("%s#2/7", knxAddressString))
readRequest, err = readRequestBuilder.Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr = readRequest.Execute()
readResult = <-rrr
if readResult.GetErr() != nil {
return nil, errors.Wrap(readResult.GetErr(), "error reading the group address association table address")
}
if (readResult.GetResponse() != nil) &&
(readResult.GetResponse().GetResponseCode("groupAddressAssociationTableAddress") != apiModel.PlcResponseCode_OK) {
return nil, errors.Errorf("error reading the group address association table address: %s",
readResult.GetResponse().GetResponseCode("groupAddressAssociationTableAddress").GetName())
}
groupAddressAssociationTableAddress := readResult.GetResponse().GetValue("groupAddressAssociationTableAddress").GetUint16()
// Then read one uint16 at the given location.
// This will return the number of entries in the group address table (each 2 bytes)
readRequestBuilder = m.connection.ReadRequestBuilder()
if m.connection.DeviceConnections[knxAddress].deviceDescriptor == uint16(0x07B0) /* SystemB */ {
readRequestBuilder.AddTagAddress("numberOfGroupAddressAssociationTableEntries",
fmt.Sprintf("%s#%X:UINT", knxAddressString, groupAddressAssociationTableAddress))
} else {
readRequestBuilder.AddTagAddress("numberOfGroupAddressAssociationTableEntries",
fmt.Sprintf("%s#%X:USINT", knxAddressString, groupAddressAssociationTableAddress))
}
readRequest, err = readRequestBuilder.Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr = readRequest.Execute()
readResult = <-rrr
if readResult.GetErr() != nil {
return nil, errors.Wrap(readResult.GetErr(), "error reading the number of group address association table entries")
}
if (readResult.GetResponse() != nil) &&
(readResult.GetResponse().GetResponseCode("numberOfGroupAddressAssociationTableEntries") != apiModel.PlcResponseCode_OK) {
return nil, errors.Errorf("error reading the number of group address association table entries: %s",
readResult.GetResponse().GetResponseCode("numberOfGroupAddressAssociationTableEntries").GetName())
}
numberOfGroupAddressAssociationTableEntries := readResult.GetResponse().GetValue("numberOfGroupAddressAssociationTableEntries").GetUint16()
// Read the data in the group address table
readRequestBuilder = m.connection.ReadRequestBuilder()
// TODO: This request needs to be automatically split up into multiple requests.
// Reasons for splitting up:
// - Max APDU Size exceeded
// - Max 63 bytes readable in one request, due to max of count tag
if m.connection.DeviceConnections[knxAddress].deviceDescriptor == uint16(0x07B0) /* SystemB */ {
readRequestBuilder.AddTagAddress("groupAddressAssociationTable",
fmt.Sprintf("%s#%X:UDINT[%d]", knxAddressString, groupAddressAssociationTableAddress+2, numberOfGroupAddressAssociationTableEntries))
} else {
readRequestBuilder.AddTagAddress("groupAddressAssociationTable",
fmt.Sprintf("%s#%X:UINT[%d]", knxAddressString, groupAddressAssociationTableAddress+1, numberOfGroupAddressAssociationTableEntries))
}
readRequest, err = readRequestBuilder.Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr = readRequest.Execute()
readResult = <-rrr
if readResult.GetErr() != nil {
return nil, errors.Wrap(readResult.GetErr(), "error reading the group address association table content")
}
if (readResult.GetResponse() != nil) &&
(readResult.GetResponse().GetResponseCode("groupAddressAssociationTable") != apiModel.PlcResponseCode_OK) {
return nil, errors.Errorf("error reading the group address association table content: %s",
readResult.GetResponse().GetResponseCode("groupAddressAssociationTable").GetName())
}
// Output the group addresses
groupAddressComObjectNumberMapping := map[driverModel.KnxGroupAddress]uint16{}
if readResult.GetResponse().GetValue("groupAddressAssociationTable").IsList() {
for _, groupAddressAssociation := range readResult.GetResponse().GetValue("groupAddressAssociationTable").GetList() {
groupAddress, comObjectNumber := m.parseAssociationTable(m.connection.DeviceConnections[knxAddress].deviceDescriptor,
knxGroupAddresses, groupAddressAssociation)
if groupAddress != nil {
groupAddressComObjectNumberMapping[groupAddress] = comObjectNumber
}
}
} else {
groupAddress, comObjectNumber := m.parseAssociationTable(m.connection.DeviceConnections[knxAddress].deviceDescriptor,
knxGroupAddresses, readResult.GetResponse().GetValue("groupAddressAssociationTable"))
if groupAddress != nil {
groupAddressComObjectNumberMapping[groupAddress] = comObjectNumber
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////
// Com Object Table reading (Not supported on all devices)
// (This part is optional and experimental ...)
/////////////////////////////////////////////////////////////////////////////////////////////////////
// In case of System B devices, the com object table is read as a property array
// In this case we can even read only the com objects we're interested in.
if m.connection.DeviceConnections[knxAddress].deviceDescriptor == uint16(0x07B0) /* SystemB */ {
readRequestBuilder = m.connection.ReadRequestBuilder()
// Read data for all com objects that are assigned a group address
for _, comObjectNumber := range groupAddressComObjectNumberMapping {
readRequestBuilder.AddTagAddress(strconv.Itoa(int(comObjectNumber)),
fmt.Sprintf("%s#3/23/%d", knxAddressString, comObjectNumber))
}
readRequest, err = readRequestBuilder.Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr = readRequest.Execute()
readResult = <-rrr
for groupAddress, comObjectNumber := range groupAddressComObjectNumberMapping {
if readResult.GetResponse().GetResponseCode(strconv.Itoa(int(comObjectNumber))) != apiModel.PlcResponseCode_OK {
continue
}
comObjectSettings := readResult.GetResponse().GetValue(strconv.Itoa(int(comObjectNumber))).GetUint16()
data := []uint8{uint8((comObjectSettings >> 8) & 0xFF), uint8(comObjectSettings & 0xFF)}
descriptor, err := driverModel.GroupObjectDescriptorRealisationTypeBParse(data)
if err != nil {
m.log.Info().Err(err).Msg("error parsing com object descriptor")
continue
}
// Assemble a PlcBrowseFoundTag
var tag apiModel.PlcTag
communicationEnable := descriptor.GetCommunicationEnable()
readable := communicationEnable && descriptor.GetReadEnable()
writable := communicationEnable && descriptor.GetWriteEnable()
subscribable := communicationEnable && descriptor.GetTransmitEnable()
// Find a matching datatype for the given value-type.
tagType := m.getTagTypeForValueType(descriptor.GetValueType())
switch groupAddress := groupAddress.(type) {
case driverModel.KnxGroupAddress3Level:
address3Level := groupAddress
tag = NewGroupAddress3LevelPlcTag(strconv.Itoa(int(address3Level.GetMainGroup())),
strconv.Itoa(int(address3Level.GetMiddleGroup())), strconv.Itoa(int(address3Level.GetSubGroup())),
&tagType)
case driverModel.KnxGroupAddress2Level:
address2Level := groupAddress
tag = NewGroupAddress2LevelPlcTag(strconv.Itoa(int(address2Level.GetMainGroup())),
strconv.Itoa(int(address2Level.GetSubGroup())),
&tagType)
case driverModel.KnxGroupAddressFreeLevel:
address1Level := groupAddress
tag = NewGroupAddress1LevelPlcTag(strconv.Itoa(int(address1Level.GetSubGroup())),
&tagType)
}
results = append(results, spiModel.NewDefaultPlcBrowseItem(
tag,
fmt.Sprintf("#%d", comObjectNumber),
"",
readable,
writable,
subscribable,
nil,
nil,
))
}
} else if (m.connection.DeviceConnections[knxAddress].deviceDescriptor & 0xFFF0) == uint16(0x0700) /* System7 */ {
// For System 7 Devices we unfortunately can't access the information of where the memory address for the
// Com Object Table is programmatically, so we have to look up the address which is extracted from the XML data
// Provided by the manufacturer. Unfortunately in order to be able to do this, we need to get the application
// version from the device first.
readRequestBuilder = m.connection.ReadRequestBuilder()
readRequestBuilder.AddTagAddress("applicationProgramVersion", knxAddressString+"#3/13")
readRequestBuilder.AddTagAddress("interfaceProgramVersion", knxAddressString+"#4/13")
readRequest, err = readRequestBuilder.Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr = readRequest.Execute()
readRequestResult := <-rrr
readResponse := readRequestResult.GetResponse()
var programVersionData []byte
if readResponse.GetResponseCode("applicationProgramVersion") == apiModel.PlcResponseCode_OK {
programVersionData = utils.PlcValueUint8ListToByteArray(readResponse.GetValue("applicationProgramVersion"))
} else if readResponse.GetResponseCode("interfaceProgramVersion") == apiModel.PlcResponseCode_OK {
programVersionData = utils.PlcValueUint8ListToByteArray(readResponse.GetValue("interfaceProgramVersion"))
}
applicationId := hex.EncodeToString(programVersionData)
// Lookup the com object table address
comObjectTableAddresses, _ := driverModel.ComObjectTableAddressesByName("DEV" + strings.ToUpper(applicationId))
if comObjectTableAddresses == 0 {
return nil, errors.Errorf("error getting com address table address. No table entry for application id: %s", applicationId)
}
readRequestBuilder = m.connection.ReadRequestBuilder()
// Read data for all com objects that are assigned a group address
groupAddressMap := map[uint16][]driverModel.KnxGroupAddress{}
for groupAddress, comObjectNumber := range groupAddressComObjectNumberMapping {
if groupAddressMap[comObjectNumber] == nil {
groupAddressMap[comObjectNumber] = []driverModel.KnxGroupAddress{}
}
groupAddressMap[comObjectNumber] = append(groupAddressMap[comObjectNumber], groupAddress)
entryAddress := comObjectTableAddresses.ComObjectTableAddress() + 3 + (comObjectNumber * 4)
readRequestBuilder.AddTagAddress(strconv.Itoa(int(comObjectNumber)),
fmt.Sprintf("%s#%X:USINT[4]", knxAddressString, entryAddress))
}
readRequest, err = readRequestBuilder.Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr = readRequest.Execute()
readResult = <-rrr
for _, tagName := range readResult.GetResponse().GetTagNames() {
array := utils.PlcValueUint8ListToByteArray(readResult.GetResponse().GetValue(tagName))
descriptor, err := driverModel.GroupObjectDescriptorRealisationType7Parse(array)
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
// We saved the com object number in the tag name.
comObjectNumber, _ := strconv.ParseUint(tagName, 10, 16)
groupAddresses := groupAddressMap[uint16(comObjectNumber)]
communicationEnable := descriptor.GetCommunicationEnable()
readable := communicationEnable && descriptor.GetReadEnable()
writable := communicationEnable && descriptor.GetWriteEnable()
subscribable := communicationEnable && descriptor.GetTransmitEnable()
// Find a matching datatype for the given value-type.
tagType := m.getTagTypeForValueType(descriptor.GetValueType())
// Create a tag for each of the given inputs.
for _, groupAddress := range groupAddresses {
tag := m.getTagForGroupAddress(groupAddress, tagType)
results = append(results, spiModel.NewDefaultPlcBrowseItem(
tag,
fmt.Sprintf("#%d", comObjectNumber),
"",
readable,
writable,
subscribable,
nil,
nil,
))
}
}
} else {
readRequestBuilder = m.connection.ReadRequestBuilder()
readRequestBuilder.AddTagAddress("comObjectTableAddress", fmt.Sprintf("%s#3/7", knxAddressString))
readRequest, err = readRequestBuilder.Build()
if err != nil {
return nil, errors.Wrap(err, "error creating read request")
}
rrr = readRequest.Execute()
readResult = <-rrr
if readResult.GetResponse().GetResponseCode("comObjectTableAddress") == apiModel.PlcResponseCode_OK {
comObjectTableAddress := readResult.GetResponse().GetValue("comObjectTableAddress").GetUint16()
m.log.Info().Msgf("Com Object Table Address: %x", comObjectTableAddress)
}
}
return results, nil
}
func (m Browser) calculateAddresses(query DeviceQuery) ([]driverModel.KnxAddress, error) {
var explodedAddresses []driverModel.KnxAddress
mainGroupOptions, err := m.explodeSegment(query.MainGroup, 1, 15)
if err != nil {
return nil, err
}
middleGroupOptions, err := m.explodeSegment(query.MiddleGroup, 1, 15)
if err != nil {
return nil, err
}
subGroupOptions, err := m.explodeSegment(query.SubGroup, 0, 255)
if err != nil {
return nil, err
}
for _, mainOption := range mainGroupOptions {
for _, middleOption := range middleGroupOptions {
for _, subOption := range subGroupOptions {
// Don't try connecting to ourselves.
if m.connection.ClientKnxAddress != nil {
currentAddress := driverModel.NewKnxAddress(
mainOption,
middleOption,
subOption,
)
explodedAddresses = append(explodedAddresses, currentAddress)
}
}
}
}
return explodedAddresses, nil
}
func (m Browser) explodeSegment(segment string, min uint8, max uint8) ([]uint8, error) {
var segmentOptions []uint8
if strings.Contains(segment, "*") {
for i := min; i <= max; i++ {
segmentOptions = append(segmentOptions, i)
}
} else if strings.HasPrefix(segment, "[") && strings.HasSuffix(segment, "]") {
segment = strings.TrimPrefix(segment, "[")
segment = strings.TrimSuffix(segment, "]")
for _, segment := range strings.Split(segment, ",") {
if strings.Contains(segment, "-") {
split := strings.Split(segment, "-")
localMin, err := strconv.ParseUint(split[0], 10, 8)
if err != nil {
return nil, err
}
localMax, err := strconv.ParseUint(split[1], 10, 8)
if err != nil {
return nil, err
}
for i := localMin; i <= localMax; i++ {
segmentOptions = append(segmentOptions, uint8(i))
}
} else {
option, err := strconv.ParseUint(segment, 10, 8)
if err != nil {
return nil, err
}
segmentOptions = append(segmentOptions, uint8(option))
}
}
} else {
value, err := strconv.ParseUint(segment, 10, 8)
if err != nil {
return nil, err
}
if uint8(value) >= min && uint8(value) <= max {
segmentOptions = append(segmentOptions, uint8(value))
}
}
return segmentOptions, nil
}
func (m Browser) parseAssociationTable(deviceDescriptor uint16, knxGroupAddresses []driverModel.KnxGroupAddress, value values.PlcValue) (driverModel.KnxGroupAddress, uint16) {
var addressIndex uint16
var comObjectNumber uint16
if deviceDescriptor == uint16(0x07B0) /* SystemB */ {
addressIndex = uint16((value.GetUint32()>>16)&0xFFFF) - 1
comObjectNumber = uint16(value.GetUint32() & 0xFFFF)
} else {
addressIndex = ((value.GetUint16() >> 8) & 0xFF) - 1
comObjectNumber = value.GetUint16() & 0xFF
}
if addressIndex < uint16(len(knxGroupAddresses)) {
groupAddress := knxGroupAddresses[addressIndex]
return groupAddress, comObjectNumber
}
return nil, 0
}
func (m Browser) getTagForGroupAddress(groupAddress driverModel.KnxGroupAddress, datatype driverModel.KnxDatapointType) apiModel.PlcTag {
switch groupAddress := groupAddress.(type) {
case driverModel.KnxGroupAddress3Level:
groupAddress3Level := groupAddress
return GroupAddress3LevelPlcTag{
MainGroup: strconv.Itoa(int(groupAddress3Level.GetMainGroup())),
MiddleGroup: strconv.Itoa(int(groupAddress3Level.GetMiddleGroup())),
SubGroup: strconv.Itoa(int(groupAddress3Level.GetSubGroup())),
TagType: &datatype,
}
case driverModel.KnxGroupAddress2Level:
groupAddress2Level := groupAddress
return GroupAddress2LevelPlcTag{
MainGroup: strconv.Itoa(int(groupAddress2Level.GetMainGroup())),
SubGroup: strconv.Itoa(int(groupAddress2Level.GetSubGroup())),
TagType: &datatype,
}
case driverModel.KnxGroupAddressFreeLevel:
groupAddress1Level := groupAddress
return GroupAddress1LevelPlcTag{
MainGroup: strconv.Itoa(int(groupAddress1Level.GetSubGroup())),
TagType: &datatype,
}
}
return nil
}
func (m Browser) getTagTypeForValueType(valueType driverModel.ComObjectValueType) driverModel.KnxDatapointType {
switch valueType {
case driverModel.ComObjectValueType_BIT1:
return driverModel.KnxDatapointType_BOOL
case driverModel.ComObjectValueType_BIT2:
// Will be an array
return driverModel.KnxDatapointType_BOOL
case driverModel.ComObjectValueType_BIT3:
// Will be an array
return driverModel.KnxDatapointType_BOOL
case driverModel.ComObjectValueType_BIT4:
// Will be an array
return driverModel.KnxDatapointType_BOOL
case driverModel.ComObjectValueType_BIT5:
// Will be an array
return driverModel.KnxDatapointType_BOOL
case driverModel.ComObjectValueType_BIT6:
// Will be an array
return driverModel.KnxDatapointType_BOOL
case driverModel.ComObjectValueType_BIT7:
// Will be an array
return driverModel.KnxDatapointType_BOOL
case driverModel.ComObjectValueType_BYTE1:
return driverModel.KnxDatapointType_USINT
case driverModel.ComObjectValueType_BYTE2:
return driverModel.KnxDatapointType_UINT
case driverModel.ComObjectValueType_BYTE3:
return driverModel.KnxDatapointType_UDINT
case driverModel.ComObjectValueType_BYTE4:
return driverModel.KnxDatapointType_UDINT
case driverModel.ComObjectValueType_BYTE6:
// Will be an array
return driverModel.KnxDatapointType_USINT
case driverModel.ComObjectValueType_BYTE8:
// Will be an array
return driverModel.KnxDatapointType_USINT
case driverModel.ComObjectValueType_BYTE10:
// Will be an array
return driverModel.KnxDatapointType_USINT
case driverModel.ComObjectValueType_BYTE14:
// Will be an array
return driverModel.KnxDatapointType_USINT
}
// Just return "byte" in any other case.
return driverModel.KnxDatapointType_USINT
}