You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2022/08/08 16:00:33 UTC

[plc4x] 04/04: feat(plc4go/cbus): first draft of browse functionality

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

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

commit c7e1d90c13d2b32e2e54686f2440e6fecd66bf93
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Mon Aug 8 18:00:22 2022 +0200

    feat(plc4go/cbus): first draft of browse functionality
---
 plc4go/internal/cbus/Browser.go                    | 69 ++++++++++++++++++++--
 plc4go/internal/cbus/Configuration.go              | 40 ++++++++++---
 plc4go/internal/cbus/Connection.go                 | 14 +++--
 plc4go/internal/cbus/Driver.go                     |  2 +-
 plc4go/internal/cbus/FieldHandler.go               | 12 ++--
 plc4go/internal/cbus/MessageCodec.go               |  5 +-
 plc4go/internal/cbus/Reader.go                     | 26 +++++---
 plc4go/internal/knxnetip/Browser.go                | 14 ++---
 .../spi/model/DefaultPlcBrowseQueryResult.go       | 66 +++++++++++++++++++++
 .../internal/spi/model/DefaultPlcBrowseRequest.go  |  1 +
 .../tests/drivers/tests/manual_cbus_driver_test.go | 30 ++++++++++
 plc4go/tools/plc4xbrowser/commands.go              |  2 +-
 12 files changed, 239 insertions(+), 42 deletions(-)

diff --git a/plc4go/internal/cbus/Browser.go b/plc4go/internal/cbus/Browser.go
index 39c5ccbe0..d2abc351f 100644
--- a/plc4go/internal/cbus/Browser.go
+++ b/plc4go/internal/cbus/Browser.go
@@ -20,8 +20,13 @@
 package cbus
 
 import (
+	"fmt"
 	"github.com/apache/plc4x/plc4go/internal/spi"
+	"github.com/apache/plc4x/plc4go/internal/spi/model"
 	apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
+	"github.com/apache/plc4x/plc4go/pkg/api/values"
+	readWriteModel "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
+	"github.com/rs/zerolog/log"
 )
 
 type Browser struct {
@@ -39,16 +44,72 @@ func NewBrowser(connection *Connection, messageCodec spi.MessageCodec) *Browser
 }
 
 func (m Browser) Browse(browseRequest apiModel.PlcBrowseRequest) <-chan apiModel.PlcBrowseRequestResult {
-	return m.BrowseWithInterceptor(browseRequest, func(result apiModel.PlcBrowseEvent) bool {
-		return true
-	})
+	return m.BrowseWithInterceptor(browseRequest, nil)
 }
 
 func (m Browser) BrowseWithInterceptor(browseRequest apiModel.PlcBrowseRequest, interceptor func(result apiModel.PlcBrowseEvent) bool) <-chan apiModel.PlcBrowseRequestResult {
 	result := make(chan apiModel.PlcBrowseRequestResult)
 
 	go func() {
-		// TODO: implement me
+		responseCodes := map[string]apiModel.PlcResponseCode{}
+		results := map[string][]apiModel.PlcBrowseFoundField{}
+		for _, fieldName := range browseRequest.GetFieldNames() {
+			field := browseRequest.GetField(fieldName)
+
+			var queryResults []apiModel.PlcBrowseFoundField
+			switch field := field.(type) {
+			case *unitInfoField:
+				var units []readWriteModel.UnitAddress
+				var attributes []readWriteModel.Attribute
+				if unitAddress := field.unitAddress; unitAddress != nil {
+					units = append(units, *unitAddress)
+				} else {
+					for i := 0; i <= 0xFF; i++ {
+						units = append(units, readWriteModel.NewUnitAddress(byte(i)))
+					}
+				}
+				if attribute := field.attribute; attribute != nil {
+					attributes = append(attributes, *attribute)
+				} else {
+					for _, attribute := range readWriteModel.AttributeValues {
+						attributes = append(attributes, attribute)
+					}
+				}
+			unitLoop:
+				for _, unit := range units {
+					for _, attribute := range attributes {
+						unitAddress := unit.GetAddress()
+						log.Info().Msgf("unit %d: Query %s", unitAddress, attribute)
+						readFieldName := fmt.Sprintf("%s/%d/%s", fieldName, unitAddress, attribute)
+						readRequest, _ := m.connection.ReadRequestBuilder().
+							AddField(readFieldName, NewCALIdentifyField(unit, attribute, 1)).
+							Build()
+						requestResult := <-readRequest.Execute()
+						if err := requestResult.GetErr(); err != nil {
+							log.Info().Err(err).Msgf("unit %d: Can't read attribute %s", unitAddress, attribute)
+							continue unitLoop
+						}
+						queryResults = append(queryResults, &model.DefaultPlcBrowseQueryResult{
+							Field:        NewCALIdentifyField(unit, attribute, 1),
+							Name:         fieldName,
+							Readable:     true,
+							Writable:     false,
+							Subscribable: false,
+							Attributes: map[string]values.PlcValue{
+								"CurrentValue": requestResult.GetResponse().GetValue(readFieldName),
+							},
+						})
+					}
+				}
+			default:
+				responseCodes[fieldName] = apiModel.PlcResponseCode_INTERNAL_ERROR
+			}
+		}
+		result <- &model.DefaultPlcBrowseRequestResult{
+			Request:  browseRequest,
+			Response: model.NewDefaultPlcBrowseResponse(browseRequest, results, responseCodes),
+			Err:      nil,
+		}
 	}()
 	return result
 }
diff --git a/plc4go/internal/cbus/Configuration.go b/plc4go/internal/cbus/Configuration.go
index 39f7911ee..0a807368c 100644
--- a/plc4go/internal/cbus/Configuration.go
+++ b/plc4go/internal/cbus/Configuration.go
@@ -20,6 +20,7 @@
 package cbus
 
 import (
+	"reflect"
 	"strconv"
 
 	"github.com/pkg/errors"
@@ -27,21 +28,46 @@ import (
 )
 
 type Configuration struct {
-	srchk bool
+	Srchk    bool
+	Exstat   bool
+	Pun      bool
+	LocalSal bool
+	Pcn      bool
+	Idmon    bool
+	Monitor  bool
+	Smart    bool
+	XonXoff  bool
+	Connect  bool
 }
 
 func ParseFromOptions(options map[string][]string) (Configuration, error) {
-	configuration := Configuration{}
-	if srchk := getFromOptions(options, "srchk"); srchk != "" {
-		parseBool, err := strconv.ParseBool(srchk)
-		if err != nil {
-			return Configuration{}, errors.Wrap(err, "Error parsing srchk")
+	configuration := createDefaultConfiguration()
+	reflectConfiguration := reflect.ValueOf(&configuration).Elem()
+	for i := 0; i < reflectConfiguration.NumField(); i++ {
+		key := reflectConfiguration.Type().Field(i).Name
+		if optionValue := getFromOptions(options, key); optionValue != "" {
+			parseBool, err := strconv.ParseBool(optionValue)
+			if err != nil {
+				return Configuration{}, errors.Wrapf(err, "Error parsing %s", key)
+			}
+			reflectConfiguration.FieldByName(key).SetBool(parseBool)
 		}
-		configuration.srchk = parseBool
 	}
 	return configuration, nil
 }
 
+func createDefaultConfiguration() Configuration {
+	return Configuration{
+		Exstat:   true,
+		LocalSal: true,
+		Idmon:    true,
+		Monitor:  true,
+		Smart:    true,
+		Srchk:    true,
+		Connect:  true,
+	}
+}
+
 func getFromOptions(options map[string][]string, key string) string {
 	if optionValues, ok := options[key]; ok {
 		if len(optionValues) <= 0 {
diff --git a/plc4go/internal/cbus/Connection.go b/plc4go/internal/cbus/Connection.go
index 5bed3562c..bcab1c603 100644
--- a/plc4go/internal/cbus/Connection.go
+++ b/plc4go/internal/cbus/Connection.go
@@ -243,30 +243,32 @@ func (c *Connection) setupConnection(ch chan plc4go.PlcConnectionConnectResult)
 	}
 	{
 		log.Debug().Msg("Set interface options 3")
-		interfaceOptions3 := readWriteModel.NewParameterValueInterfaceOptions3(readWriteModel.NewInterfaceOptions3(true, false, true, false), nil, 1)
+		interfaceOptions3 := readWriteModel.NewParameterValueInterfaceOptions3(readWriteModel.NewInterfaceOptions3(c.configuration.Exstat, c.configuration.Pun, c.configuration.LocalSal, c.configuration.Pcn), nil, 1)
 		if !c.sendCalDataWrite(ch, readWriteModel.Parameter_INTERFACE_OPTIONS_3, interfaceOptions3, requestContext, cbusOptions) {
 			return
 		}
 		// TODO: add localsal to the options
-		*cbusOptions = readWriteModel.NewCBusOptions(false, false, false, true, false, false, false, false, false)
+		*cbusOptions = readWriteModel.NewCBusOptions(false, false, false, c.configuration.Exstat, false, false, c.configuration.Pun, c.configuration.Pcn, false)
 		log.Debug().Msg("Interface options 3 set")
 	}
 	{
 		log.Debug().Msg("Set interface options 1 power up settings")
-		interfaceOptions1PowerUpSettings := readWriteModel.NewParameterValueInterfaceOptions1PowerUpSettings(readWriteModel.NewInterfaceOptions1PowerUpSettings(readWriteModel.NewInterfaceOptions1(true, true, true, true, false, true)), 1)
+		interfaceOptions1PowerUpSettings := readWriteModel.NewParameterValueInterfaceOptions1PowerUpSettings(readWriteModel.NewInterfaceOptions1PowerUpSettings(readWriteModel.NewInterfaceOptions1(c.configuration.Idmon, c.configuration.Monitor, c.configuration.Smart, c.configuration.Srchk, c.configuration.XonXoff, c.configuration.Connect)), 1)
 		if !c.sendCalDataWrite(ch, readWriteModel.Parameter_INTERFACE_OPTIONS_1_POWER_UP_SETTINGS, interfaceOptions1PowerUpSettings, requestContext, cbusOptions) {
 			return
 		}
-		*cbusOptions = readWriteModel.NewCBusOptions(true, true, true, true, true, false, false, false, true)
+		// TODO: what is with monall
+		*cbusOptions = readWriteModel.NewCBusOptions(c.configuration.Connect, c.configuration.Smart, c.configuration.Idmon, c.configuration.Exstat, c.configuration.Monitor, false, c.configuration.Pun, c.configuration.Pcn, c.configuration.Srchk)
 		log.Debug().Msg("Interface options 1 power up settings set")
 	}
 	{
 		log.Debug().Msg("Set interface options 1")
-		interfaceOptions1 := readWriteModel.NewParameterValueInterfaceOptions1(readWriteModel.NewInterfaceOptions1(true, true, true, true, false, true), nil, 1)
+		interfaceOptions1 := readWriteModel.NewParameterValueInterfaceOptions1(readWriteModel.NewInterfaceOptions1(c.configuration.Idmon, c.configuration.Monitor, c.configuration.Smart, c.configuration.Srchk, c.configuration.XonXoff, c.configuration.Connect), nil, 1)
 		if !c.sendCalDataWrite(ch, readWriteModel.Parameter_INTERFACE_OPTIONS_1, interfaceOptions1, requestContext, cbusOptions) {
 			return
 		}
-		*cbusOptions = readWriteModel.NewCBusOptions(true, true, true, true, true, false, false, false, true)
+		// TODO: what is with monall
+		*cbusOptions = readWriteModel.NewCBusOptions(c.configuration.Connect, c.configuration.Smart, c.configuration.Idmon, c.configuration.Exstat, c.configuration.Monitor, false, c.configuration.Pun, c.configuration.Pcn, c.configuration.Srchk)
 		log.Debug().Msg("Interface options 1 set")
 	}
 	c.fireConnected(ch)
diff --git a/plc4go/internal/cbus/Driver.go b/plc4go/internal/cbus/Driver.go
index 83164bd23..5f736d97b 100644
--- a/plc4go/internal/cbus/Driver.go
+++ b/plc4go/internal/cbus/Driver.go
@@ -82,7 +82,7 @@ func (m *Driver) GetConnection(transportUrl url.URL, transports map[string]trans
 		return ch
 	}
 
-	codec := NewMessageCodec(transportInstance, configuration.srchk)
+	codec := NewMessageCodec(transportInstance)
 	log.Debug().Msgf("working with codec %#v", codec)
 
 	driverContext, err := NewDriverContext(configuration)
diff --git a/plc4go/internal/cbus/FieldHandler.go b/plc4go/internal/cbus/FieldHandler.go
index c62cb7a8f..dc5330259 100644
--- a/plc4go/internal/cbus/FieldHandler.go
+++ b/plc4go/internal/cbus/FieldHandler.go
@@ -58,10 +58,10 @@ type FieldHandler struct {
 func NewFieldHandler() FieldHandler {
 	return FieldHandler{
 		statusRequestPattern: regexp.MustCompile(`^status/(?P<statusRequestType>(?P<binary>binary)|level=0x(?P<startingGroupAddressLabel>00|20|40|60|80|A0|C0|E0))/(?P<application>.*)`),
-		calPattern:           regexp.MustCompile(`^cal/(?P<unitAddress>.*)/(?P<calType>recall=\[(?P<recallParamNo>\w+), ?(?P<recallCount>\d+)]|identify=(?P<identifyAttribute>\w+)|getstatus=(?P<getstatusParamNo>\w+), ?(?P<getstatusCount>\d+))`),
-		salMonitorPattern:    regexp.MustCompile(`^salmonitor/(?P<unitAddress>.*)/(?P<application>.*)`),
-		mmiMonitorPattern:    regexp.MustCompile(`^mmimonitor/(?P<unitAddress>.*)/(?P<application>.*)`),
-		unityQuery:           regexp.MustCompile(`^info/(?P<unitAddress>.*)/(?P<identifyAttribute>\w+)`),
+		calPattern:           regexp.MustCompile(`^cal/(?P<unitAddress>.+)/(?P<calType>recall=\[(?P<recallParamNo>\w+), ?(?P<recallCount>\d+)]|identify=(?P<identifyAttribute>\w+)|getstatus=(?P<getstatusParamNo>\w+), ?(?P<getstatusCount>\d+))`),
+		salMonitorPattern:    regexp.MustCompile(`^salmonitor/(?P<unitAddress>.+)/(?P<application>.+)`),
+		mmiMonitorPattern:    regexp.MustCompile(`^mmimonitor/(?P<unitAddress>.+)/(?P<application>.+)`),
+		unityQuery:           regexp.MustCompile(`^info/(?P<unitAddress>.+)/(?P<identifyAttribute>.+)`),
 	}
 }
 
@@ -306,7 +306,9 @@ func (m FieldHandler) ParseQuery(query string) (model.PlcField, error) {
 
 		var attribute *readWriteModel.Attribute
 		attributeArgument := match["identifyAttribute"]
-		if strings.HasPrefix(attributeArgument, "0x") {
+		if attributeArgument == "*" {
+			attribute = nil
+		} else if strings.HasPrefix(attributeArgument, "0x") {
 			decodedHex, err := hex.DecodeString(attributeArgument[2:])
 			if err != nil {
 				return nil, errors.Wrap(err, "Not a valid hex")
diff --git a/plc4go/internal/cbus/MessageCodec.go b/plc4go/internal/cbus/MessageCodec.go
index bd9738a29..4c3a912db 100644
--- a/plc4go/internal/cbus/MessageCodec.go
+++ b/plc4go/internal/cbus/MessageCodec.go
@@ -42,10 +42,10 @@ type MessageCodec struct {
 	hashEncountered uint
 }
 
-func NewMessageCodec(transportInstance transports.TransportInstance, srchk bool) *MessageCodec {
+func NewMessageCodec(transportInstance transports.TransportInstance) *MessageCodec {
 	codec := &MessageCodec{
 		requestContext: readwriteModel.NewRequestContext(false),
-		cbusOptions:    readwriteModel.NewCBusOptions(false, false, false, false, false, false, false, false, srchk),
+		cbusOptions:    readwriteModel.NewCBusOptions(false, false, false, false, false, false, false, false, false),
 		monitoredSALs:  make(chan readwriteModel.MonitoredSAL, 100),
 	}
 	codec.DefaultCodec = _default.NewDefaultCodec(codec, transportInstance, _default.WithCustomMessageHandler(func(codec _default.DefaultCodecRequirements, message spi.Message) bool {
@@ -192,6 +192,7 @@ lookingForTheEnd:
 	rb := utils.NewReadBufferByteBased(read)
 	cBusMessage, err := readwriteModel.CBusMessageParse(rb, pciResponse, m.requestContext, m.cbusOptions)
 	if err != nil {
+		log.Debug().Err(err).Msg("First Parse Failed")
 		{ // Try SAL
 			rb := utils.NewReadBufferByteBased(read)
 			cBusMessage, secondErr := readwriteModel.CBusMessageParse(rb, pciResponse, readwriteModel.NewRequestContext(false), m.cbusOptions)
diff --git a/plc4go/internal/cbus/Reader.go b/plc4go/internal/cbus/Reader.go
index 8df3d1b18..8c87455a0 100644
--- a/plc4go/internal/cbus/Reader.go
+++ b/plc4go/internal/cbus/Reader.go
@@ -119,12 +119,10 @@ func (m *Reader) Read(readRequest model.PlcReadRequest) <-chan model.PlcReadRequ
 						log.Trace().Msg("convert response to ")
 						cbusMessage := receivedMessage.(readWriteModel.CBusMessage)
 						messageToClient := cbusMessage.(readWriteModel.CBusMessageToClient)
-						confirmation := messageToClient.GetReply().(readWriteModel.ReplyOrConfirmationConfirmationExactly)
-						if !confirmation.GetConfirmation().GetIsSuccess() {
+						replyOrConfirmationConfirmation := messageToClient.GetReply().(readWriteModel.ReplyOrConfirmationConfirmationExactly)
+						if !replyOrConfirmationConfirmation.GetConfirmation().GetIsSuccess() {
 							var responseCode model.PlcResponseCode
-							switch confirmation.GetConfirmation().GetConfirmationType() {
-							case readWriteModel.ConfirmationType_CONFIRMATION_SUCCESSFUL:
-								responseCode = model.PlcResponseCode_OK
+							switch replyOrConfirmationConfirmation.GetConfirmation().GetConfirmationType() {
 							case readWriteModel.ConfirmationType_NOT_TRANSMITTED_TO_MANY_RE_TRANSMISSIONS:
 								responseCode = model.PlcResponseCode_REMOTE_ERROR
 							case readWriteModel.ConfirmationType_NOT_TRANSMITTED_CORRUPTION:
@@ -133,14 +131,26 @@ func (m *Reader) Read(readRequest model.PlcReadRequest) <-chan model.PlcReadRequ
 								responseCode = model.PlcResponseCode_REMOTE_BUSY
 							case readWriteModel.ConfirmationType_NOT_TRANSMITTED_TOO_LONG:
 								responseCode = model.PlcResponseCode_INVALID_DATA
+							default:
+								panic("Every code should be mapped here")
 							}
+							log.Trace().Msgf("Was no success %s:%s", fieldNameCopy, responseCode)
 							addResponseCode(fieldNameCopy, responseCode)
-							return nil
+							requestWasOk <- true
+							return transaction.EndRequest()
 						}
 
+						alpha := replyOrConfirmationConfirmation.GetConfirmation().GetAlpha()
 						// TODO: it could be double confirmed but this is not implemented yet
-						embeddedReply := confirmation.GetEmbeddedReply().(readWriteModel.ReplyOrConfirmationReplyExactly)
+						embeddedReply, ok := replyOrConfirmationConfirmation.GetEmbeddedReply().(readWriteModel.ReplyOrConfirmationReplyExactly)
+						if !ok {
+							log.Trace().Msgf("Is a confirm only, no data. Alpha: %c", alpha.GetCharacter())
+							addResponseCode(fieldNameCopy, model.PlcResponseCode_NOT_FOUND)
+							requestWasOk <- true
+							return transaction.EndRequest()
+						}
 
+						log.Trace().Msg("Handling confirmed data")
 						switch reply := embeddedReply.GetReply().(readWriteModel.ReplyEncodedReply).GetEncodedReply().(type) {
 						case readWriteModel.EncodedReplyCALReplyExactly:
 							calData := reply.GetCalReply().GetCalData()
@@ -210,6 +220,8 @@ func (m *Reader) Read(readRequest model.PlcReadRequest) <-chan model.PlcReadRequ
 							}
 							// TODO: how should we serialize that???
 							addPlcValue(fieldNameCopy, spiValues.NewPlcSTRING(fmt.Sprintf("%s", calData)))
+						default:
+							panic(fmt.Sprintf("All types should be mapped here. Not mapped: %T", reply))
 						}
 						requestWasOk <- true
 						return transaction.EndRequest()
diff --git a/plc4go/internal/knxnetip/Browser.go b/plc4go/internal/knxnetip/Browser.go
index 247ca225b..3702158d4 100644
--- a/plc4go/internal/knxnetip/Browser.go
+++ b/plc4go/internal/knxnetip/Browser.go
@@ -58,14 +58,6 @@ func (m Browser) Browse(browseRequest apiModel.PlcBrowseRequest) <-chan apiModel
 
 func (m Browser) BrowseWithInterceptor(browseRequest apiModel.PlcBrowseRequest, interceptor func(result apiModel.PlcBrowseEvent) bool) <-chan apiModel.PlcBrowseRequestResult {
 	result := make(chan apiModel.PlcBrowseRequestResult)
-	sendResult := func(browseResponse apiModel.PlcBrowseResponse, err error) {
-		result <- &model.DefaultPlcBrowseRequestResult{
-			Request:  browseRequest,
-			Response: browseResponse,
-			Err:      err,
-		}
-	}
-
 	go func() {
 		responseCodes := map[string]apiModel.PlcResponseCode{}
 		results := map[string][]apiModel.PlcBrowseFoundField{}
@@ -93,7 +85,11 @@ func (m Browser) BrowseWithInterceptor(browseRequest apiModel.PlcBrowseRequest,
 				responseCodes[fieldName] = apiModel.PlcResponseCode_INTERNAL_ERROR
 			}
 		}
-		sendResult(model.NewDefaultPlcBrowseResponse(browseRequest, results, responseCodes), nil)
+		result <- &model.DefaultPlcBrowseRequestResult{
+			Request:  browseRequest,
+			Response: model.NewDefaultPlcBrowseResponse(browseRequest, results, responseCodes),
+			Err:      nil,
+		}
 	}()
 	return result
 }
diff --git a/plc4go/internal/spi/model/DefaultPlcBrowseQueryResult.go b/plc4go/internal/spi/model/DefaultPlcBrowseQueryResult.go
index 8888eb868..21bd11a9b 100644
--- a/plc4go/internal/spi/model/DefaultPlcBrowseQueryResult.go
+++ b/plc4go/internal/spi/model/DefaultPlcBrowseQueryResult.go
@@ -20,6 +20,8 @@
 package model
 
 import (
+	"fmt"
+	"github.com/apache/plc4x/plc4go/internal/spi/utils"
 	"github.com/apache/plc4x/plc4go/pkg/api/model"
 	"github.com/apache/plc4x/plc4go/pkg/api/values"
 )
@@ -61,3 +63,67 @@ func (d *DefaultPlcBrowseQueryResult) GetPossibleDataTypes() []string {
 func (d *DefaultPlcBrowseQueryResult) GetAttributes() map[string]values.PlcValue {
 	return d.Attributes
 }
+
+func (d *DefaultPlcBrowseQueryResult) Serialize(writeBuffer utils.WriteBuffer) error {
+	if err := writeBuffer.PushContext("PlcBrowseQueryResult"); err != nil {
+		return err
+	}
+
+	var fieldAsString string
+	fieldAsString = fmt.Sprintf("%s", d.Field)
+	if err := writeBuffer.WriteString("field", uint32(len(fieldAsString)*8), "UTF-8", fieldAsString); err != nil {
+		return err
+	}
+	if err := writeBuffer.WriteString("name", uint32(len(d.Name)*8), "UTF-8", d.Name); err != nil {
+		return err
+	}
+	if err := writeBuffer.WriteBit("readable", d.Readable); err != nil {
+		return err
+	}
+	if err := writeBuffer.WriteBit("writable", d.Writable); err != nil {
+		return err
+	}
+	if err := writeBuffer.WriteBit("subscribable", d.Subscribable); err != nil {
+		return err
+	}
+	if err := writeBuffer.PushContext("possibleDataTypes", utils.WithRenderAsList(true)); err != nil {
+		return err
+	}
+	for _, dataType := range d.PossibleDataTypes {
+		if err := writeBuffer.WriteString("", uint32(len(dataType)*8), "UTF-8", dataType); err != nil {
+			return err
+		}
+	}
+	if err := writeBuffer.PopContext("possibleDataTypes", utils.WithRenderAsList(true)); err != nil {
+		return err
+	}
+	if err := writeBuffer.PushContext("attributes", utils.WithRenderAsList(true)); err != nil {
+		return err
+	}
+	for name, plcValue := range d.Attributes {
+		if serializable, ok := plcValue.(utils.Serializable); ok {
+			if err := writeBuffer.PushContext(name); err != nil {
+				return err
+			}
+			if err := serializable.Serialize(writeBuffer); err != nil {
+				return err
+			}
+			if err := writeBuffer.PopContext(name); err != nil {
+				return err
+			}
+		} else {
+			plcValueAsString := fmt.Sprintf("%v", plcValue)
+			if err := writeBuffer.WriteString(name, uint32(len(plcValueAsString)*8), "UTF-8", plcValueAsString); err != nil {
+				return err
+			}
+		}
+	}
+	if err := writeBuffer.PopContext("attributes", utils.WithRenderAsList(true)); err != nil {
+		return err
+	}
+
+	if err := writeBuffer.PopContext("PlcBrowseQueryResult"); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/plc4go/internal/spi/model/DefaultPlcBrowseRequest.go b/plc4go/internal/spi/model/DefaultPlcBrowseRequest.go
index beeb2fbc4..580cef577 100644
--- a/plc4go/internal/spi/model/DefaultPlcBrowseRequest.go
+++ b/plc4go/internal/spi/model/DefaultPlcBrowseRequest.go
@@ -40,6 +40,7 @@ func NewDefaultPlcBrowseRequestBuilder(fieldHandler spi.PlcFieldHandler, browser
 		fieldHandler: fieldHandler,
 		browser:      browser,
 		queries:      map[string]string{},
+		fields:       map[string]model.PlcField{},
 	}
 }
 
diff --git a/plc4go/tests/drivers/tests/manual_cbus_driver_test.go b/plc4go/tests/drivers/tests/manual_cbus_driver_test.go
index 57bd809c8..0f8302b9e 100644
--- a/plc4go/tests/drivers/tests/manual_cbus_driver_test.go
+++ b/plc4go/tests/drivers/tests/manual_cbus_driver_test.go
@@ -93,3 +93,33 @@ func TestManualCBusDriver(t *testing.T) {
 		t.Logf("Got %d monitors", monitorCount)
 	})
 }
+
+func TestManualCBusBrowse(t *testing.T) {
+	log.Logger = log.
+		With().Caller().Logger().
+		Output(zerolog.ConsoleWriter{Out: os.Stderr}).
+		Level(zerolog.TraceLevel)
+	config.TraceTransactionManagerWorkers = true
+	config.TraceTransactionManagerTransactions = true
+	config.TraceDefaultMessageCodecWorker = true
+	t.Skip()
+
+	connectionString := "c-bus://192.168.178.101?Monitor=false"
+	driverManager := plc4go.NewPlcDriverManager()
+	driverManager.RegisterDriver(cbus.NewDriver())
+	transports.RegisterTcpTransport(driverManager)
+	connectionResult := <-driverManager.GetConnection(connectionString)
+	if err := connectionResult.GetErr(); err != nil {
+		panic(err)
+	}
+	connection := connectionResult.GetConnection()
+	defer connection.Close()
+	browseRequest, err := connection.BrowseRequestBuilder().
+		AddQuery("asd", "info/*/*").
+		Build()
+	if err != nil {
+		panic(err)
+	}
+	browseRequestResult := <-browseRequest.Execute()
+	fmt.Printf("%s", browseRequestResult)
+}
diff --git a/plc4go/tools/plc4xbrowser/commands.go b/plc4go/tools/plc4xbrowser/commands.go
index 0241a659d..44fbe8829 100644
--- a/plc4go/tools/plc4xbrowser/commands.go
+++ b/plc4go/tools/plc4xbrowser/commands.go
@@ -264,7 +264,7 @@ var rootCommand = Command{
 				} else {
 					start := time.Now()
 					browseRequest, err := connection.BrowseRequestBuilder().
-						AddQuery("writeField", split[1]).
+						AddQuery("browseField", split[1]).
 						Build()
 					if err != nil {
 						return errors.Wrapf(err, "%s can't browse", connectionsString)