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/18 10:39:21 UTC

[plc4x] branch develop updated (4e8e0ef6d -> 5fd053d92)

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

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


    from 4e8e0ef6d build(deps): bump woodstox-core from 6.3.0 to 6.3.1 (#453)
     new 88de0758a feat(plc4go): Treat a single element PlcValueList as PlcValue
     new 5fd053d92 feat(plc4go/cbus): first implementation of writer support

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 plc4go/internal/cbus/CBusMessageFactory.go | 206 +++++++++++++
 plc4go/internal/cbus/FieldHandler.go       | 478 +++++++++++++++--------------
 plc4go/internal/cbus/Reader.go             | 144 +++------
 plc4go/internal/cbus/Writer.go             | 110 ++++++-
 plc4go/spi/values/PlcList.go               | 156 +++++++++-
 5 files changed, 754 insertions(+), 340 deletions(-)
 create mode 100644 plc4go/internal/cbus/CBusMessageFactory.go


[plc4x] 02/02: feat(plc4go/cbus): first implementation of writer support

Posted by sr...@apache.org.
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 5fd053d923cb483895b4ae9cb46fa9b54e64d8f1
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Thu Aug 18 12:39:10 2022 +0200

    feat(plc4go/cbus): first implementation of writer support
---
 plc4go/internal/cbus/CBusMessageFactory.go | 206 +++++++++++++
 plc4go/internal/cbus/FieldHandler.go       | 478 +++++++++++++++--------------
 plc4go/internal/cbus/Reader.go             | 144 +++------
 plc4go/internal/cbus/Writer.go             | 110 ++++++-
 4 files changed, 609 insertions(+), 329 deletions(-)

diff --git a/plc4go/internal/cbus/CBusMessageFactory.go b/plc4go/internal/cbus/CBusMessageFactory.go
new file mode 100644
index 000000000..1b8cb1811
--- /dev/null
+++ b/plc4go/internal/cbus/CBusMessageFactory.go
@@ -0,0 +1,206 @@
+/*
+ * 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 cbus
+
+import (
+	"fmt"
+	"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/pkg/errors"
+	"strings"
+)
+
+func FieldToCBusMessage(field model.PlcField, value values.PlcValue, alphaGenerator *AlphaGenerator, messageCodec *MessageCodec) (cBusMessage readWriteModel.CBusMessage, supportsRead, supportsWrite, supportsSubscribe bool, err error) {
+	cbusOptions := messageCodec.cbusOptions
+	requestContext := messageCodec.requestContext
+	switch field := field.(type) {
+	case *statusField:
+		var statusRequest readWriteModel.StatusRequest
+		switch field.statusRequestType {
+		case StatusRequestTypeBinaryState:
+			statusRequest = readWriteModel.NewStatusRequestBinaryState(field.application, 0x7A)
+		case StatusRequestTypeLevel:
+			statusRequest = readWriteModel.NewStatusRequestLevel(field.application, *field.startingGroupAddressLabel, 0x73)
+		}
+		command := readWriteModel.NewCBusPointToMultiPointCommandStatus(statusRequest, byte(field.application), cbusOptions)
+		header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToMultiPoint)
+		cbusCommand := readWriteModel.NewCBusCommandPointToMultiPoint(command, header, cbusOptions)
+		request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+
+		cBusMessage, supportsRead, supportsSubscribe = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), true, true
+		return
+	case *calRecallField:
+		calData := readWriteModel.NewCALDataRecall(field.parameter, field.count, readWriteModel.CALCommandTypeContainer_CALCommandRecall, nil, requestContext)
+		//TODO: we need support for bridged commands
+		command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
+		header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
+		cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
+		request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+
+		cBusMessage, supportsRead = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), true
+		return
+	case *calIdentifyField:
+		calData := readWriteModel.NewCALDataIdentify(field.attribute, readWriteModel.CALCommandTypeContainer_CALCommandIdentify, nil, requestContext)
+		//TODO: we need support for bridged commands
+		command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
+		header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
+		cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
+		request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+
+		cBusMessage, supportsRead = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), true
+		return
+	case *calGetstatusField:
+		calData := readWriteModel.NewCALDataGetStatus(field.parameter, field.count, readWriteModel.CALCommandTypeContainer_CALCommandGetStatus, nil, requestContext)
+		//TODO: we need support for bridged commands
+		command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
+		header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
+		cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
+		request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+
+		cBusMessage, supportsRead = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), true
+		return
+	case *salField:
+		var salCommand = field.salCommand
+		if salCommand == "" {
+			return nil, false, false, false, errors.New("Empty sal command not supported")
+		}
+		var salData readWriteModel.SALData
+		switch field.application.ApplicationId() {
+		case readWriteModel.ApplicationId_FREE_USAGE:
+			panic("Not yet implemented") // TODO: implement
+		case readWriteModel.ApplicationId_TEMPERATURE_BROADCAST:
+			var temperatureBroadcastData readWriteModel.TemperatureBroadcastData
+			switch salCommand {
+			case readWriteModel.TemperatureBroadcastCommandType_BROADCAST_EVENT.PLC4XEnumName():
+				if value == nil || !value.IsList() || len(value.GetList()) != 2 || !value.GetList()[0].IsByte() || !value.GetList()[1].IsByte() {
+					return nil, false, false, false, errors.Errorf("%s requires exactly 2 arguments [temperatureGroup,temperatureByte]", salCommand)
+				}
+				commandTypeContainer := readWriteModel.TemperatureBroadcastCommandTypeContainer_TemperatureBroadcastCommandSetBroadcastEvent1_2Bytes
+				temperatureGroup := value.GetList()[0].GetByte()
+				temperatureByte := value.GetList()[1].GetByte()
+				temperatureBroadcastData = readWriteModel.NewTemperatureBroadcastData(commandTypeContainer, temperatureGroup, temperatureByte)
+				supportsWrite = true
+			default:
+				return nil, false, false, false, errors.Errorf("Unsupported command %s for %s", salCommand, field.application.ApplicationId())
+			}
+			salData = readWriteModel.NewSALDataTemperatureBroadcast(temperatureBroadcastData, nil)
+		case readWriteModel.ApplicationId_ROOM_CONTROL_SYSTEM:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_LIGHTING:
+			// TODO: is this more are write?? maybe we a wrong here at the reader
+			var lightingData readWriteModel.LightingData
+			switch salCommand {
+			case readWriteModel.LightingCommandType_OFF.PLC4XEnumName():
+				commandTypeContainer := readWriteModel.LightingCommandTypeContainer_LightingCommandOff
+				if value == nil || !value.IsByte() {
+					return nil, false, false, false, errors.Errorf("%s requires exactly 1 arguments [group]", salCommand)
+				}
+				group := value.GetByte()
+				lightingData = readWriteModel.NewLightingDataOff(group, commandTypeContainer)
+				supportsWrite = true
+			case readWriteModel.LightingCommandType_ON.PLC4XEnumName():
+				commandTypeContainer := readWriteModel.LightingCommandTypeContainer_LightingCommandOn
+				if value == nil || (!value.IsByte() && (!value.IsList() || len(value.GetList()) != 1 || value.GetList()[0].IsByte())) {
+					return nil, false, false, false, errors.Errorf("%s requires exactly 1 arguments [group]", salCommand)
+				}
+				group := value.GetByte()
+				lightingData = readWriteModel.NewLightingDataOn(group, commandTypeContainer)
+				supportsWrite = true
+			case readWriteModel.LightingCommandType_RAMP_TO_LEVEL.PLC4XEnumName():
+				if value == nil || !value.IsList() || len(value.GetList()) != 3 || !value.GetList()[0].IsString() || !value.GetList()[1].IsByte() || !value.GetList()[2].IsByte() {
+					return nil, false, false, false, errors.Errorf("%s requires exactly 2 arguments [delay,group,level]", salCommand)
+				}
+				commandTypeContainer, ok := readWriteModel.LightingCommandTypeContainerByName(fmt.Sprintf("LightingCommandRampToLevel_%s", value.GetList()[0].GetString()))
+				if !ok {
+					var possibleValues []string
+					for _, v := range readWriteModel.LightingCommandTypeContainerValues {
+						possibleValues = append(possibleValues, strings.TrimPrefix(v.String(), "LightingCommandRampToLevel_"))
+					}
+					return nil, false, false, false, errors.Errorf("No level found for %s. Possible values %s", value.GetList()[0].GetString(), possibleValues)
+				}
+				group := value.GetList()[1].GetByte()
+				level := value.GetList()[2].GetByte()
+				lightingData = readWriteModel.NewLightingDataRampToLevel(group, level, commandTypeContainer)
+				supportsWrite = true
+			case readWriteModel.LightingCommandType_TERMINATE_RAMP.PLC4XEnumName():
+				commandTypeContainer := readWriteModel.LightingCommandTypeContainer_LightingCommandTerminateRamp
+				if value == nil || !value.IsByte() {
+					return nil, false, false, false, errors.Errorf("%s requires exactly 1 arguments [group]", salCommand)
+				}
+				group := value.GetByte()
+				lightingData = readWriteModel.NewLightingDataTerminateRamp(group, commandTypeContainer)
+				supportsWrite = true
+			case readWriteModel.LightingCommandType_LABEL.PLC4XEnumName():
+				panic("Implement me")
+			default:
+				return nil, false, false, false, errors.Errorf("Unsupported command %s for %s", salCommand, field.application.ApplicationId())
+			}
+			salData = readWriteModel.NewSALDataLighting(lightingData, nil)
+		case readWriteModel.ApplicationId_VENTILATION:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_IRRIGATION_CONTROL:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_POOLS_SPAS_PONDS_FOUNTAINS_CONTROL:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_HEATING:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_AIR_CONDITIONING:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_TRIGGER_CONTROL:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_ENABLE_CONTROL:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_AUDIO_AND_VIDEO:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_SECURITY:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_METERING:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_ACCESS_CONTROL:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_CLOCK_AND_TIMEKEEPING:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_TELEPHONY_STATUS_AND_CONTROL:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_MEASUREMENT:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_TESTING:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_MEDIA_TRANSPORT_CONTROL:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_ERROR_REPORTING:
+			panic("Implement me")
+		case readWriteModel.ApplicationId_HVAC_ACTUATOR:
+			panic("Implement me")
+		default:
+			return nil, false, false, false, errors.Errorf("No support for %s", field.application)
+		}
+		//TODO: we need support for bridged commands
+		command := readWriteModel.NewCBusPointToMultiPointCommandNormal(field.application, salData, 0x00, cbusOptions)
+		header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
+		cbusCommand := readWriteModel.NewCBusCommandPointToMultiPoint(command, header, cbusOptions)
+		request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
+		cBusMessage = readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions)
+		return
+	default:
+		return nil, false, false, false, errors.Errorf("Unsupported type %T", field)
+	}
+}
diff --git a/plc4go/internal/cbus/FieldHandler.go b/plc4go/internal/cbus/FieldHandler.go
index 0efbda82e..adfdff9e0 100644
--- a/plc4go/internal/cbus/FieldHandler.go
+++ b/plc4go/internal/cbus/FieldHandler.go
@@ -105,236 +105,182 @@ var PossibleSalCommands = map[readWriteModel.ApplicationId][]string{
 
 func (m FieldHandler) ParseQuery(query string) (model.PlcField, error) {
 	if match := utils.GetSubgroupMatches(m.statusRequestPattern, query); match != nil {
-		var startingGroupAddressLabel *byte
-		var statusRequestType StatusRequestType
-		statusRequestArgument := match["statusRequestType"]
-		if statusRequestArgument != "" {
-			if match["binary"] != "" {
-				statusRequestType = StatusRequestTypeBinaryState
-			} else if levelArgument := match["startingGroupAddressLabel"]; levelArgument != "" {
-				statusRequestType = StatusRequestTypeLevel
-				startingGroupAddressLabelArgument := match["startingGroupAddressLabel"]
-				decodedHex, _ := hex.DecodeString(startingGroupAddressLabelArgument)
-				if len(decodedHex) != 1 {
-					panic("invalid state. Should have exactly 1")
-				}
-				startingGroupAddressLabel = &decodedHex[0]
-			} else {
-				return nil, errors.Errorf("Unknown statusRequestType%s", statusRequestArgument)
+		return m.handleStatusRequestPattern(match)
+	} else if match := utils.GetSubgroupMatches(m.calPattern, query); match != nil {
+		return m.handleCalPattern(match)
+	} else if match := utils.GetSubgroupMatches(m.salPattern, query); match != nil {
+		return m.handleSALPattern(match)
+	} else if match := utils.GetSubgroupMatches(m.salMonitorPattern, query); match != nil {
+		return m.handleSALMonitorPattern(match)
+	} else if match := utils.GetSubgroupMatches(m.mmiMonitorPattern, query); match != nil {
+		return m.handleMMIMonitorPattern(match)
+	} else if match := utils.GetSubgroupMatches(m.unityQuery, query); match != nil {
+		return m.handleUnitQuery(match)
+	} else {
+		return nil, errors.Errorf("Unable to parse %s", query)
+	}
+}
+
+func (m FieldHandler) handleStatusRequestPattern(match map[string]string) (model.PlcField, error) {
+	var startingGroupAddressLabel *byte
+	var statusRequestType StatusRequestType
+	statusRequestArgument := match["statusRequestType"]
+	if statusRequestArgument != "" {
+		if match["binary"] != "" {
+			statusRequestType = StatusRequestTypeBinaryState
+		} else if levelArgument := match["startingGroupAddressLabel"]; levelArgument != "" {
+			statusRequestType = StatusRequestTypeLevel
+			startingGroupAddressLabelArgument := match["startingGroupAddressLabel"]
+			decodedHex, _ := hex.DecodeString(startingGroupAddressLabelArgument)
+			if len(decodedHex) != 1 {
+				panic("invalid state. Should have exactly 1")
 			}
+			startingGroupAddressLabel = &decodedHex[0]
+		} else {
+			return nil, errors.Errorf("Unknown statusRequestType%s", statusRequestArgument)
 		}
-		application, err := applicationIdFromArgument(match["application"])
+	}
+	application, err := applicationIdFromArgument(match["application"])
+	if err != nil {
+		return nil, errors.Wrap(err, "Error getting application id from argument")
+	}
+	return NewStatusField(statusRequestType, startingGroupAddressLabel, application, 1), nil
+}
+
+func (m FieldHandler) handleCalPattern(match map[string]string) (model.PlcField, error) {
+	var unitAddress readWriteModel.UnitAddress
+	unitAddressArgument := match["unitAddress"]
+	if strings.HasPrefix(unitAddressArgument, "0x") {
+		decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
 		if err != nil {
-			return nil, errors.Wrap(err, "Error getting application id from argument")
+			return nil, errors.Wrap(err, "Not a valid hex")
 		}
-		return NewStatusField(statusRequestType, startingGroupAddressLabel, application, 1), nil
-	} else if match := utils.GetSubgroupMatches(m.calPattern, query); match != nil {
-		var unitAddress readWriteModel.UnitAddress
-		unitAddressArgument := match["unitAddress"]
-		if strings.HasPrefix(unitAddressArgument, "0x") {
-			decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
+		if len(decodedHex) != 1 {
+			return nil, errors.Errorf("Hex must be exatly one byte")
+		}
+		unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
+	} else {
+		atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
+		if err != nil {
+			return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
+		}
+		unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
+	}
+
+	calTypeArgument := match["calType"]
+	switch {
+	case strings.HasPrefix(calTypeArgument, "recall="):
+		var recalParamNo readWriteModel.Parameter
+		recallParamNoArgument := match["recallParamNo"]
+		if strings.HasPrefix(recallParamNoArgument, "0x") {
+			decodedHex, err := hex.DecodeString(recallParamNoArgument[2:])
 			if err != nil {
 				return nil, errors.Wrap(err, "Not a valid hex")
 			}
 			if len(decodedHex) != 1 {
 				return nil, errors.Errorf("Hex must be exatly one byte")
 			}
-			unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
+			recalParamNo = readWriteModel.Parameter(decodedHex[0])
 		} else {
-			atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
-			if err != nil {
-				return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
-			}
-			unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
-		}
 
-		calTypeArgument := match["calType"]
-		switch {
-		case strings.HasPrefix(calTypeArgument, "recall="):
-			var recalParamNo readWriteModel.Parameter
-			recallParamNoArgument := match["recallParamNo"]
-			if strings.HasPrefix(recallParamNoArgument, "0x") {
-				decodedHex, err := hex.DecodeString(recallParamNoArgument[2:])
-				if err != nil {
-					return nil, errors.Wrap(err, "Not a valid hex")
-				}
-				if len(decodedHex) != 1 {
-					return nil, errors.Errorf("Hex must be exatly one byte")
-				}
-				recalParamNo = readWriteModel.Parameter(decodedHex[0])
+			if atoi, err := strconv.ParseUint(recallParamNoArgument, 10, 8); err == nil {
+				recalParamNo = readWriteModel.Parameter(atoi)
 			} else {
-
-				if atoi, err := strconv.ParseUint(recallParamNoArgument, 10, 8); err == nil {
-					recalParamNo = readWriteModel.Parameter(atoi)
-				} else {
-					parameterByName, ok := readWriteModel.ParameterByName(recallParamNoArgument)
-					if !ok {
-						return nil, errors.Errorf("Unknown recallParamNo %s", recallParamNoArgument)
-					}
-					recalParamNo = parameterByName
+				parameterByName, ok := readWriteModel.ParameterByName(recallParamNoArgument)
+				if !ok {
+					return nil, errors.Errorf("Unknown recallParamNo %s", recallParamNoArgument)
 				}
+				recalParamNo = parameterByName
 			}
-			var count uint8
-			atoi, err := strconv.ParseUint(match["recallCount"], 10, 8)
+		}
+		var count uint8
+		atoi, err := strconv.ParseUint(match["recallCount"], 10, 8)
+		if err != nil {
+			return nil, errors.Wrap(err, "recallCount not a valid number")
+		}
+		count = uint8(atoi)
+		return NewCALRecallField(unitAddress, recalParamNo, count, 1), nil
+	case strings.HasPrefix(calTypeArgument, "identify="):
+		var attribute readWriteModel.Attribute
+		attributeArgument := match["identifyAttribute"]
+		if strings.HasPrefix(attributeArgument, "0x") {
+			decodedHex, err := hex.DecodeString(attributeArgument[2:])
 			if err != nil {
-				return nil, errors.Wrap(err, "recallCount not a valid number")
+				return nil, errors.Wrap(err, "Not a valid hex")
 			}
-			count = uint8(atoi)
-			return NewCALRecallField(unitAddress, recalParamNo, count, 1), nil
-		case strings.HasPrefix(calTypeArgument, "identify="):
-			var attribute readWriteModel.Attribute
-			attributeArgument := match["identifyAttribute"]
-			if strings.HasPrefix(attributeArgument, "0x") {
-				decodedHex, err := hex.DecodeString(attributeArgument[2:])
-				if err != nil {
-					return nil, errors.Wrap(err, "Not a valid hex")
-				}
-				if len(decodedHex) != 1 {
-					return nil, errors.Errorf("Hex must be exatly one byte")
-				}
-				attribute = readWriteModel.Attribute(decodedHex[0])
-			} else {
-				if atoi, err := strconv.ParseUint(attributeArgument, 10, 8); err == nil {
-					attribute = readWriteModel.Attribute(atoi)
-				} else {
-					parameterByName, ok := readWriteModel.AttributeByName(attributeArgument)
-					if !ok {
-						return nil, errors.Errorf("Unknown attributeArgument %s", attributeArgument)
-					}
-					attribute = parameterByName
-				}
+			if len(decodedHex) != 1 {
+				return nil, errors.Errorf("Hex must be exatly one byte")
 			}
-			return NewCALIdentifyField(unitAddress, attribute, 1), nil
-		case strings.HasPrefix(calTypeArgument, "getstatus="):
-			var recalParamNo readWriteModel.Parameter
-			recallParamNoArgument := match["getstatusParamNo"]
-			if strings.HasPrefix(recallParamNoArgument, "0x") {
-				decodedHex, err := hex.DecodeString(recallParamNoArgument[2:])
-				if err != nil {
-					return nil, errors.Wrap(err, "Not a valid hex")
-				}
-				if len(decodedHex) != 1 {
-					return nil, errors.Errorf("Hex must be exatly one byte")
-				}
-				recalParamNo = readWriteModel.Parameter(decodedHex[0])
+			attribute = readWriteModel.Attribute(decodedHex[0])
+		} else {
+			if atoi, err := strconv.ParseUint(attributeArgument, 10, 8); err == nil {
+				attribute = readWriteModel.Attribute(atoi)
 			} else {
-				if atoi, err := strconv.ParseUint(recallParamNoArgument, 10, 8); err == nil {
-					recalParamNo = readWriteModel.Parameter(atoi)
-				} else {
-					parameterByName, ok := readWriteModel.ParameterByName(recallParamNoArgument)
-					if !ok {
-						return nil, errors.Errorf("Unknown getstatusParamNo %s", recallParamNoArgument)
-					}
-					recalParamNo = parameterByName
+				parameterByName, ok := readWriteModel.AttributeByName(attributeArgument)
+				if !ok {
+					return nil, errors.Errorf("Unknown attributeArgument %s", attributeArgument)
 				}
+				attribute = parameterByName
 			}
-			var count uint8
-			atoi, err := strconv.ParseUint(match["getstatusCount"], 10, 8)
+		}
+		return NewCALIdentifyField(unitAddress, attribute, 1), nil
+	case strings.HasPrefix(calTypeArgument, "getstatus="):
+		var recalParamNo readWriteModel.Parameter
+		recallParamNoArgument := match["getstatusParamNo"]
+		if strings.HasPrefix(recallParamNoArgument, "0x") {
+			decodedHex, err := hex.DecodeString(recallParamNoArgument[2:])
 			if err != nil {
-				return nil, errors.Wrap(err, "getstatusCount not a valid number")
+				return nil, errors.Wrap(err, "Not a valid hex")
 			}
-			count = uint8(atoi)
-			return NewCALGetstatusField(unitAddress, recalParamNo, count, 1), nil
-		default:
-			return nil, errors.Errorf("Invalid cal type %s", calTypeArgument)
-		}
-	} else if match := utils.GetSubgroupMatches(m.salPattern, query); match != nil {
-		application, err := applicationIdFromArgument(match["application"])
-		if err != nil {
-			return nil, errors.Wrap(err, "Error getting application id from argument")
-		}
-		salCommand := match["salCommand"]
-		if salCommand == "" {
-			return nil, errors.Wrap(err, "Error getting salCommand from argument")
-		}
-		isValid := false
-		for _, request := range PossibleSalCommands[application.ApplicationId()] {
-			isValid = isValid || strings.HasPrefix(salCommand, request)
-		}
-		if !isValid {
-			return nil, errors.Errorf("Invalid sal command %s for %s. Allowed requests: %s", salCommand, application, PossibleSalCommands[application.ApplicationId()])
-		}
-		panic("Implement me")
-	} else if match := utils.GetSubgroupMatches(m.salMonitorPattern, query); match != nil {
-		var unitAddress readWriteModel.UnitAddress
-		{
-			unitAddressArgument := match["unitAddress"]
-			if unitAddressArgument == "*" {
-				unitAddress = nil
-			} else if strings.HasPrefix(unitAddressArgument, "0x") {
-				decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
-				if err != nil {
-					return nil, errors.Wrap(err, "Not a valid hex")
-				}
-				if len(decodedHex) != 1 {
-					return nil, errors.Errorf("Hex must be exatly one byte")
-				}
-				unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
-			} else {
-				atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
-				if err != nil {
-					return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
-				}
-				unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
+			if len(decodedHex) != 1 {
+				return nil, errors.Errorf("Hex must be exatly one byte")
 			}
-		}
-
-		var application readWriteModel.ApplicationIdContainer
-		{
-			applicationIdArgument := match["application"]
-			if applicationIdArgument == "*" {
-				application = readWriteModel.ApplicationIdContainer_RESERVED_FF
+			recalParamNo = readWriteModel.Parameter(decodedHex[0])
+		} else {
+			if atoi, err := strconv.ParseUint(recallParamNoArgument, 10, 8); err == nil {
+				recalParamNo = readWriteModel.Parameter(atoi)
 			} else {
-				var err error
-				application, err = applicationIdFromArgument(applicationIdArgument)
-				if err != nil {
-					return nil, errors.Wrap(err, "Error getting application id from argument")
+				parameterByName, ok := readWriteModel.ParameterByName(recallParamNoArgument)
+				if !ok {
+					return nil, errors.Errorf("Unknown getstatusParamNo %s", recallParamNoArgument)
 				}
+				recalParamNo = parameterByName
 			}
 		}
-
-		return NewSALMonitorField(unitAddress, application, 1), nil
-	} else if match := utils.GetSubgroupMatches(m.mmiMonitorPattern, query); match != nil {
-		var unitAddress readWriteModel.UnitAddress
-		{
-			unitAddressArgument := match["unitAddress"]
-			if unitAddressArgument == "*" {
-				unitAddress = nil
-			} else if strings.HasPrefix(unitAddressArgument, "0x") {
-				decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
-				if err != nil {
-					return nil, errors.Wrap(err, "Not a valid hex")
-				}
-				if len(decodedHex) != 1 {
-					return nil, errors.Errorf("Hex must be exatly one byte")
-				}
-				unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
-			} else {
-				atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
-				if err != nil {
-					return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
-				}
-				unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
-			}
+		var count uint8
+		atoi, err := strconv.ParseUint(match["getstatusCount"], 10, 8)
+		if err != nil {
+			return nil, errors.Wrap(err, "getstatusCount not a valid number")
 		}
+		count = uint8(atoi)
+		return NewCALGetstatusField(unitAddress, recalParamNo, count, 1), nil
+	default:
+		return nil, errors.Errorf("Invalid cal type %s", calTypeArgument)
+	}
+}
 
-		var application readWriteModel.ApplicationIdContainer
-		{
-			applicationIdArgument := match["application"]
-			if applicationIdArgument == "*" {
-				application = readWriteModel.ApplicationIdContainer_RESERVED_FF
-			} else {
-				var err error
-				application, err = applicationIdFromArgument(applicationIdArgument)
-				if err != nil {
-					return nil, errors.Wrap(err, "Error getting application id from argument")
-				}
-			}
-		}
+func (m FieldHandler) handleSALPattern(match map[string]string) (model.PlcField, error) {
+	application, err := applicationIdFromArgument(match["application"])
+	if err != nil {
+		return nil, errors.Wrap(err, "Error getting application id from argument")
+	}
+	salCommand := match["salCommand"]
+	if salCommand == "" {
+		return nil, errors.Wrap(err, "Error getting salCommand from argument")
+	}
+	isValid := false
+	for _, request := range PossibleSalCommands[application.ApplicationId()] {
+		isValid = isValid || strings.HasPrefix(salCommand, request)
+	}
+	if !isValid {
+		return nil, errors.Errorf("Invalid sal command %s for %s. Allowed requests: %s", salCommand, application, PossibleSalCommands[application.ApplicationId()])
+	}
+	return NewSALField(application, salCommand, 1), nil
+}
 
-		return NewMMIMonitorField(unitAddress, application, 1), nil
-	} else if match := utils.GetSubgroupMatches(m.unityQuery, query); match != nil {
-		var unitAddress *readWriteModel.UnitAddress
+func (m FieldHandler) handleSALMonitorPattern(match map[string]string) (model.PlcField, error) {
+	var unitAddress readWriteModel.UnitAddress
+	{
 		unitAddressArgument := match["unitAddress"]
 		if unitAddressArgument == "*" {
 			unitAddress = nil
@@ -346,53 +292,131 @@ func (m FieldHandler) ParseQuery(query string) (model.PlcField, error) {
 			if len(decodedHex) != 1 {
 				return nil, errors.Errorf("Hex must be exatly one byte")
 			}
-			var unitAddressVar readWriteModel.UnitAddress
-			unitAddressVar = readWriteModel.NewUnitAddress(decodedHex[0])
-			unitAddress = &unitAddressVar
+			unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
 		} else {
 			atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
 			if err != nil {
 				return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
 			}
-			var unitAddressVar readWriteModel.UnitAddress
-			unitAddressVar = readWriteModel.NewUnitAddress(byte(atoi))
-			unitAddress = &unitAddressVar
+			unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
 		}
+	}
 
-		var attribute *readWriteModel.Attribute
-		attributeArgument := match["identifyAttribute"]
-		if attributeArgument == "*" {
-			attribute = nil
-		} else if strings.HasPrefix(attributeArgument, "0x") {
-			decodedHex, err := hex.DecodeString(attributeArgument[2:])
+	var application readWriteModel.ApplicationIdContainer
+	{
+		applicationIdArgument := match["application"]
+		if applicationIdArgument == "*" {
+			application = readWriteModel.ApplicationIdContainer_RESERVED_FF
+		} else {
+			var err error
+			application, err = applicationIdFromArgument(applicationIdArgument)
+			if err != nil {
+				return nil, errors.Wrap(err, "Error getting application id from argument")
+			}
+		}
+	}
+
+	return NewSALMonitorField(unitAddress, application, 1), nil
+}
+
+func (m FieldHandler) handleMMIMonitorPattern(match map[string]string) (model.PlcField, error) {
+	var unitAddress readWriteModel.UnitAddress
+	{
+		unitAddressArgument := match["unitAddress"]
+		if unitAddressArgument == "*" {
+			unitAddress = nil
+		} else if strings.HasPrefix(unitAddressArgument, "0x") {
+			decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
 			if err != nil {
 				return nil, errors.Wrap(err, "Not a valid hex")
 			}
 			if len(decodedHex) != 1 {
 				return nil, errors.Errorf("Hex must be exatly one byte")
 			}
+			unitAddress = readWriteModel.NewUnitAddress(decodedHex[0])
+		} else {
+			atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
+			if err != nil {
+				return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
+			}
+			unitAddress = readWriteModel.NewUnitAddress(byte(atoi))
+		}
+	}
+
+	var application readWriteModel.ApplicationIdContainer
+	{
+		applicationIdArgument := match["application"]
+		if applicationIdArgument == "*" {
+			application = readWriteModel.ApplicationIdContainer_RESERVED_FF
+		} else {
+			var err error
+			application, err = applicationIdFromArgument(applicationIdArgument)
+			if err != nil {
+				return nil, errors.Wrap(err, "Error getting application id from argument")
+			}
+		}
+	}
+
+	return NewMMIMonitorField(unitAddress, application, 1), nil
+}
+
+func (m FieldHandler) handleUnitQuery(match map[string]string) (model.PlcField, error) {
+	var unitAddress *readWriteModel.UnitAddress
+	unitAddressArgument := match["unitAddress"]
+	if unitAddressArgument == "*" {
+		unitAddress = nil
+	} else if strings.HasPrefix(unitAddressArgument, "0x") {
+		decodedHex, err := hex.DecodeString(unitAddressArgument[2:])
+		if err != nil {
+			return nil, errors.Wrap(err, "Not a valid hex")
+		}
+		if len(decodedHex) != 1 {
+			return nil, errors.Errorf("Hex must be exatly one byte")
+		}
+		var unitAddressVar readWriteModel.UnitAddress
+		unitAddressVar = readWriteModel.NewUnitAddress(decodedHex[0])
+		unitAddress = &unitAddressVar
+	} else {
+		atoi, err := strconv.ParseUint(unitAddressArgument, 10, 8)
+		if err != nil {
+			return nil, errors.Errorf("Unknown unit address %s", unitAddressArgument)
+		}
+		var unitAddressVar readWriteModel.UnitAddress
+		unitAddressVar = readWriteModel.NewUnitAddress(byte(atoi))
+		unitAddress = &unitAddressVar
+	}
+
+	var attribute *readWriteModel.Attribute
+	attributeArgument := match["identifyAttribute"]
+	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")
+		}
+		if len(decodedHex) != 1 {
+			return nil, errors.Errorf("Hex must be exatly one byte")
+		}
+		var attributeVar readWriteModel.Attribute
+		attributeVar = readWriteModel.Attribute(decodedHex[0])
+		attribute = &attributeVar
+	} else {
+		if atoi, err := strconv.ParseUint(attributeArgument, 10, 8); err == nil {
 			var attributeVar readWriteModel.Attribute
-			attributeVar = readWriteModel.Attribute(decodedHex[0])
+			attributeVar = readWriteModel.Attribute(atoi)
 			attribute = &attributeVar
 		} else {
-			if atoi, err := strconv.ParseUint(attributeArgument, 10, 8); err == nil {
-				var attributeVar readWriteModel.Attribute
-				attributeVar = readWriteModel.Attribute(atoi)
-				attribute = &attributeVar
-			} else {
-				parameterByName, ok := readWriteModel.AttributeByName(attributeArgument)
-				if !ok {
-					return nil, errors.Errorf("Unknown attributeArgument %s", attributeArgument)
-				}
-				var attributeVar readWriteModel.Attribute
-				attributeVar = parameterByName
-				attribute = &attributeVar
+			parameterByName, ok := readWriteModel.AttributeByName(attributeArgument)
+			if !ok {
+				return nil, errors.Errorf("Unknown attributeArgument %s", attributeArgument)
 			}
+			var attributeVar readWriteModel.Attribute
+			attributeVar = parameterByName
+			attribute = &attributeVar
 		}
-		return NewUnitInfoField(unitAddress, attribute, 1), nil
-	} else {
-		return nil, errors.Errorf("Unable to parse %s", query)
 	}
+	return NewUnitInfoField(unitAddress, attribute, 1), nil
 }
 
 func applicationIdFromArgument(applicationIdArgument string) (readWriteModel.ApplicationIdContainer, error) {
diff --git a/plc4go/internal/cbus/Reader.go b/plc4go/internal/cbus/Reader.go
index bbc90e79c..e0712f4a2 100644
--- a/plc4go/internal/cbus/Reader.go
+++ b/plc4go/internal/cbus/Reader.go
@@ -26,8 +26,8 @@ import (
 	"sync"
 	"time"
 
-	"github.com/apache/plc4x/plc4go/pkg/api/model"
-	"github.com/apache/plc4x/plc4go/pkg/api/values"
+	apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
+	apiValues "github.com/apache/plc4x/plc4go/pkg/api/values"
 	readWriteModel "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
 	"github.com/apache/plc4x/plc4go/spi"
 	spiModel "github.com/apache/plc4x/plc4go/spi/model"
@@ -51,13 +51,12 @@ func NewReader(tpduGenerator *AlphaGenerator, messageCodec spi.MessageCodec, tm
 	}
 }
 
-func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-chan model.PlcReadRequestResult {
-	// TODO: handle ctx
+func (m *Reader) Read(ctx context.Context, readRequest apiModel.PlcReadRequest) <-chan apiModel.PlcReadRequestResult {
 	log.Trace().Msg("Reading")
-	result := make(chan model.PlcReadRequestResult)
+	result := make(chan apiModel.PlcReadRequestResult)
 	go func() {
 		numFields := len(readRequest.GetFieldNames())
-		if numFields > 20 {
+		if numFields > 20 { // letters g-z
 			result <- &spiModel.DefaultPlcReadRequestResult{
 				Request:  readRequest,
 				Response: nil,
@@ -68,7 +67,15 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 		messages := make(map[string]readWriteModel.CBusMessage)
 		for _, fieldName := range readRequest.GetFieldNames() {
 			field := readRequest.GetField(fieldName)
-			message, err := m.fieldToCBusMessage(field)
+			message, supportsRead, _, _, err := FieldToCBusMessage(field, nil, m.alphaGenerator, m.messageCodec.(*MessageCodec))
+			if !supportsRead {
+				result <- &spiModel.DefaultPlcReadRequestResult{
+					Request:  readRequest,
+					Response: nil,
+					Err:      errors.Wrapf(err, "Error encoding cbus message for field %s. Field is not meant to be read.", fieldName),
+				}
+				return
+			}
 			if err != nil {
 				result <- &spiModel.DefaultPlcReadRequestResult{
 					Request:  readRequest,
@@ -80,26 +87,31 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 			messages[fieldName] = message
 		}
 		responseMu := sync.Mutex{}
-		responseCodes := map[string]model.PlcResponseCode{}
-		addResponseCode := func(name string, responseCode model.PlcResponseCode) {
+		responseCodes := map[string]apiModel.PlcResponseCode{}
+		addResponseCode := func(name string, responseCode apiModel.PlcResponseCode) {
 			responseMu.Lock()
 			defer responseMu.Unlock()
 			responseCodes[name] = responseCode
 		}
 		valueMu := sync.Mutex{}
-		plcValues := map[string]values.PlcValue{}
-		addPlcValue := func(name string, plcValue values.PlcValue) {
+		plcValues := map[string]apiValues.PlcValue{}
+		addPlcValue := func(name string, plcValue apiValues.PlcValue) {
 			valueMu.Lock()
 			defer valueMu.Unlock()
 			plcValues[name] = plcValue
 		}
 		for fieldName, messageToSend := range messages {
+			if err := ctx.Err(); err != nil {
+				result <- &spiModel.DefaultPlcReadRequestResult{
+					Request: readRequest,
+					Err:     err,
+				}
+				return
+			}
 			fieldNameCopy := fieldName
 			// Start a new request-transaction (Is ended in the response-handler)
-			requestWasOk := make(chan bool)
 			transaction := m.tm.StartTransaction()
 			transaction.Submit(func() {
-
 				// Send the  over the wire
 				log.Trace().Msg("Send ")
 				if err := m.messageCodec.SendRequest(ctx, messageToSend, func(receivedMessage spi.Message) bool {
@@ -129,28 +141,26 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 					messageToClient := cbusMessage.(readWriteModel.CBusMessageToClient)
 					if _, ok := messageToClient.GetReply().(readWriteModel.ServerErrorReplyExactly); ok {
 						log.Trace().Msg("We got a server failure")
-						addResponseCode(fieldNameCopy, model.PlcResponseCode_INVALID_DATA)
-						requestWasOk <- false
+						addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_INVALID_DATA)
 						return transaction.EndRequest()
 					}
 					replyOrConfirmationConfirmation := messageToClient.GetReply().(readWriteModel.ReplyOrConfirmationConfirmationExactly)
 					if !replyOrConfirmationConfirmation.GetConfirmation().GetIsSuccess() {
-						var responseCode model.PlcResponseCode
+						var responseCode apiModel.PlcResponseCode
 						switch replyOrConfirmationConfirmation.GetConfirmation().GetConfirmationType() {
 						case readWriteModel.ConfirmationType_NOT_TRANSMITTED_TO_MANY_RE_TRANSMISSIONS:
-							responseCode = model.PlcResponseCode_REMOTE_ERROR
+							responseCode = apiModel.PlcResponseCode_REMOTE_ERROR
 						case readWriteModel.ConfirmationType_NOT_TRANSMITTED_CORRUPTION:
-							responseCode = model.PlcResponseCode_INVALID_DATA
+							responseCode = apiModel.PlcResponseCode_INVALID_DATA
 						case readWriteModel.ConfirmationType_NOT_TRANSMITTED_SYNC_LOSS:
-							responseCode = model.PlcResponseCode_REMOTE_BUSY
+							responseCode = apiModel.PlcResponseCode_REMOTE_BUSY
 						case readWriteModel.ConfirmationType_NOT_TRANSMITTED_TOO_LONG:
-							responseCode = model.PlcResponseCode_INVALID_DATA
+							responseCode = apiModel.PlcResponseCode_INVALID_DATA
 						default:
 							panic("Every code should be mapped here")
 						}
 						log.Trace().Msgf("Was no success %s:%v", fieldNameCopy, responseCode)
 						addResponseCode(fieldNameCopy, responseCode)
-						requestWasOk <- true
 						return transaction.EndRequest()
 					}
 
@@ -159,8 +169,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 					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
+						addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_NOT_FOUND)
 						return transaction.EndRequest()
 					}
 
@@ -169,7 +178,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 					switch reply := embeddedReply.GetReply().(readWriteModel.ReplyEncodedReply).GetEncodedReply().(type) {
 					case readWriteModel.EncodedReplyCALReplyExactly:
 						calData := reply.GetCalReply().GetCalData()
-						addResponseCode(fieldNameCopy, model.PlcResponseCode_OK)
+						addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_OK)
 						switch calData := calData.(type) {
 						case readWriteModel.CALDataStatusExactly:
 							application := calData.GetApplication()
@@ -179,8 +188,8 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 							// TODO: verify application... this should be the same
 							_ = blockStart
 							statusBytes := calData.GetStatusBytes()
-							addResponseCode(fieldNameCopy, model.PlcResponseCode_OK)
-							plcListValues := make([]values.PlcValue, len(statusBytes)*4)
+							addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_OK)
+							plcListValues := make([]apiValues.PlcValue, len(statusBytes)*4)
 							for i, statusByte := range statusBytes {
 								plcListValues[i*4+0] = spiValues.NewPlcSTRING(statusByte.GetGav0().String())
 								plcListValues[i*4+1] = spiValues.NewPlcSTRING(statusByte.GetGav1().String())
@@ -203,8 +212,8 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 								fallthrough
 							case readWriteModel.StatusCoding_BINARY_BY_ELSEWHERE:
 								statusBytes := calData.GetStatusBytes()
-								addResponseCode(fieldNameCopy, model.PlcResponseCode_OK)
-								plcListValues := make([]values.PlcValue, len(statusBytes)*4)
+								addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_OK)
+								plcListValues := make([]apiValues.PlcValue, len(statusBytes)*4)
 								for i, statusByte := range statusBytes {
 									plcListValues[i*4+0] = spiValues.NewPlcSTRING(statusByte.GetGav0().String())
 									plcListValues[i*4+1] = spiValues.NewPlcSTRING(statusByte.GetGav1().String())
@@ -216,8 +225,8 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 								fallthrough
 							case readWriteModel.StatusCoding_LEVEL_BY_ELSEWHERE:
 								levelInformation := calData.GetLevelInformation()
-								addResponseCode(fieldNameCopy, model.PlcResponseCode_OK)
-								plcListValues := make([]values.PlcValue, len(levelInformation))
+								addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_OK)
+								plcListValues := make([]apiValues.PlcValue, len(levelInformation))
 								for i, levelInformation := range levelInformation {
 									switch levelInformation := levelInformation.(type) {
 									case readWriteModel.LevelInformationAbsentExactly:
@@ -237,12 +246,12 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 							case readWriteModel.IdentifyReplyCommandCurrentSenseLevelsExactly:
 								addPlcValue(fieldNameCopy, spiValues.NewPlcByteArray(identifyReplyCommand.GetCurrentSenseLevels()))
 							case readWriteModel.IdentifyReplyCommandDelaysExactly:
-								addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]values.PlcValue{
+								addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
 									"ReStrikeDelay": spiValues.NewPlcUINT(uint16(identifyReplyCommand.GetReStrikeDelay())),
 									"TerminalLevel": spiValues.NewPlcByteArray(identifyReplyCommand.GetTerminalLevels()),
 								}))
 							case readWriteModel.IdentifyReplyCommandDSIStatusExactly:
-								addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]values.PlcValue{
+								addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
 									"ChannelStatus1":          spiValues.NewPlcSTRING(identifyReplyCommand.GetChannelStatus1().String()),
 									"ChannelStatus2":          spiValues.NewPlcSTRING(identifyReplyCommand.GetChannelStatus2().String()),
 									"ChannelStatus3":          spiValues.NewPlcSTRING(identifyReplyCommand.GetChannelStatus3().String()),
@@ -255,7 +264,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 									"DimmingUCRevisionNumber": spiValues.NewPlcUINT(uint16(identifyReplyCommand.GetDimmingUCRevisionNumber())),
 								}))
 							case readWriteModel.IdentifyReplyCommandExtendedDiagnosticSummaryExactly:
-								addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]values.PlcValue{
+								addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
 									"LowApplication":         spiValues.NewPlcSTRING(identifyReplyCommand.GetLowApplication().String()),
 									"HighApplication":        spiValues.NewPlcSTRING(identifyReplyCommand.GetHighApplication().String()),
 									"Area":                   spiValues.NewPlcUINT(uint16(identifyReplyCommand.GetArea())),
@@ -277,7 +286,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 									"MicroPowerReset":        spiValues.NewPlcBOOL(identifyReplyCommand.GetMicroPowerReset()),
 								}))
 							case readWriteModel.IdentifyReplyCommandSummaryExactly:
-								addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]values.PlcValue{
+								addPlcValue(fieldNameCopy, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
 									"PartName":        spiValues.NewPlcSTRING(identifyReplyCommand.GetPartName()),
 									"UnitServiceType": spiValues.NewPlcUINT(uint16(identifyReplyCommand.GetUnitServiceType())),
 									"Version":         spiValues.NewPlcSTRING(identifyReplyCommand.GetVersion()),
@@ -291,9 +300,9 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 							case readWriteModel.IdentifyReplyCommandGAVValuesStoredExactly:
 								addPlcValue(fieldNameCopy, spiValues.NewPlcByteArray(identifyReplyCommand.GetValues()))
 							case readWriteModel.IdentifyReplyCommandLogicalAssignmentExactly:
-								var plcValues []values.PlcValue
+								var plcValues []apiValues.PlcValue
 								for _, logicAssigment := range identifyReplyCommand.GetLogicAssigment() {
-									plcValues = append(plcValues, spiValues.NewPlcStruct(map[string]values.PlcValue{
+									plcValues = append(plcValues, spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
 										"GreaterOfOrLogic": spiValues.NewPlcBOOL(logicAssigment.GetGreaterOfOrLogic()),
 										"ReStrikeDelay":    spiValues.NewPlcBOOL(logicAssigment.GetReStrikeDelay()),
 										"AssignedToGav16":  spiValues.NewPlcBOOL(logicAssigment.GetAssignedToGav16()),
@@ -326,8 +335,8 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 								addPlcValue(fieldNameCopy, spiValues.NewPlcLREAL(voltsFloat))
 							case readWriteModel.IdentifyReplyCommandOutputUnitSummaryExactly:
 								unitFlags := identifyReplyCommand.GetUnitFlags()
-								structContent := map[string]values.PlcValue{
-									"UnitFlags": spiValues.NewPlcStruct(map[string]values.PlcValue{
+								structContent := map[string]apiValues.PlcValue{
+									"UnitFlags": spiValues.NewPlcStruct(map[string]apiValues.PlcValue{
 										"AssertingNetworkBurden": spiValues.NewPlcBOOL(unitFlags.GetAssertingNetworkBurden()),
 										"RestrikeTimingActive":   spiValues.NewPlcBOOL(unitFlags.GetRestrikeTimingActive()),
 										"RemoteOFFInputAsserted": spiValues.NewPlcBOOL(unitFlags.GetRemoteOFFInputAsserted()),
@@ -352,7 +361,7 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 								addPlcValue(fieldNameCopy, spiValues.NewPlcSTRING(identifyReplyCommand.GetUnitType()))
 							default:
 								log.Error().Msgf("Unmapped type %T", identifyReplyCommand)
-								requestWasOk <- false
+								addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_INVALID_DATA)
 								return transaction.EndRequest()
 							}
 						default:
@@ -362,25 +371,18 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 					default:
 						panic(fmt.Sprintf("All types should be mapped here. Not mapped: %T", reply))
 					}
-					requestWasOk <- true
 					return transaction.EndRequest()
 				}, func(err error) error {
 					log.Debug().Msgf("Error waiting for field %s", fieldNameCopy)
-					addResponseCode(fieldNameCopy, model.PlcResponseCode_REQUEST_TIMEOUT)
+					addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_REQUEST_TIMEOUT)
 					// TODO: ok or not ok?
-					requestWasOk <- true
 					return transaction.EndRequest()
 				}, time.Second*1); err != nil {
 					log.Debug().Err(err).Msgf("Error sending message for field %s", fieldNameCopy)
-					addResponseCode(fieldNameCopy, model.PlcResponseCode_INTERNAL_ERROR)
+					addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_INTERNAL_ERROR)
 					_ = transaction.EndRequest()
-					requestWasOk <- false
 				}
 			})
-			if !<-requestWasOk {
-				// TODO: if we found a error we can abort
-				break
-			}
 		}
 		readResponse := spiModel.NewDefaultPlcReadResponse(readRequest, responseCodes, plcValues)
 		result <- &spiModel.DefaultPlcReadRequestResult{
@@ -390,49 +392,3 @@ func (m *Reader) Read(ctx context.Context, readRequest model.PlcReadRequest) <-c
 	}()
 	return result
 }
-
-func (m *Reader) fieldToCBusMessage(field model.PlcField) (readWriteModel.CBusMessage, error) {
-	cbusOptions := m.messageCodec.(*MessageCodec).cbusOptions
-	requestContext := m.messageCodec.(*MessageCodec).requestContext
-	switch field := field.(type) {
-	case *statusField:
-		var statusRequest readWriteModel.StatusRequest
-		switch field.statusRequestType {
-		case StatusRequestTypeBinaryState:
-			statusRequest = readWriteModel.NewStatusRequestBinaryState(field.application, 0x7A)
-		case StatusRequestTypeLevel:
-			statusRequest = readWriteModel.NewStatusRequestLevel(field.application, *field.startingGroupAddressLabel, 0x73)
-		}
-		command := readWriteModel.NewCBusPointToMultiPointCommandStatus(statusRequest, byte(field.application), cbusOptions)
-		header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToMultiPoint)
-		cbusCommand := readWriteModel.NewCBusCommandPointToMultiPoint(command, header, cbusOptions)
-		request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(m.alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
-		return readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), nil
-	case *calRecallField:
-		calData := readWriteModel.NewCALDataRecall(field.parameter, field.count, readWriteModel.CALCommandTypeContainer_CALCommandRecall, nil, requestContext)
-		//TODO: we need support for bridged commands
-		command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
-		header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
-		cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
-		request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(m.alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
-		return readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), nil
-	case *calIdentifyField:
-		calData := readWriteModel.NewCALDataIdentify(field.attribute, readWriteModel.CALCommandTypeContainer_CALCommandIdentify, nil, requestContext)
-		//TODO: we need support for bridged commands
-		command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
-		header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
-		cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
-		request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(m.alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
-		return readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), nil
-	case *calGetstatusField:
-		calData := readWriteModel.NewCALDataGetStatus(field.parameter, field.count, readWriteModel.CALCommandTypeContainer_CALCommandGetStatus, nil, requestContext)
-		//TODO: we need support for bridged commands
-		command := readWriteModel.NewCBusPointToPointCommandDirect(field.unitAddress, 0x0000, calData, cbusOptions)
-		header := readWriteModel.NewCBusHeader(readWriteModel.PriorityClass_Class4, false, 0, readWriteModel.DestinationAddressType_PointToPoint)
-		cbusCommand := readWriteModel.NewCBusCommandPointToPoint(command, header, cbusOptions)
-		request := readWriteModel.NewRequestCommand(cbusCommand, nil, readWriteModel.NewAlpha(m.alphaGenerator.getAndIncrement()), readWriteModel.RequestType_REQUEST_COMMAND, nil, nil, readWriteModel.RequestType_EMPTY, readWriteModel.NewRequestTermination(), cbusOptions)
-		return readWriteModel.NewCBusMessageToServer(request, requestContext, cbusOptions), nil
-	default:
-		return nil, errors.Errorf("Unmapped type %T", field)
-	}
-}
diff --git a/plc4go/internal/cbus/Writer.go b/plc4go/internal/cbus/Writer.go
index b901f8493..077291b6a 100644
--- a/plc4go/internal/cbus/Writer.go
+++ b/plc4go/internal/cbus/Writer.go
@@ -21,10 +21,14 @@ package cbus
 
 import (
 	"context"
-	"github.com/apache/plc4x/plc4go/pkg/api/model"
+	apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
+	readWriteModel "github.com/apache/plc4x/plc4go/protocols/cbus/readwrite/model"
 	"github.com/apache/plc4x/plc4go/spi"
-	plc4goModel "github.com/apache/plc4x/plc4go/spi/model"
+	spiModel "github.com/apache/plc4x/plc4go/spi/model"
 	"github.com/pkg/errors"
+	"github.com/rs/zerolog/log"
+	"sync"
+	"time"
 )
 
 type Writer struct {
@@ -41,14 +45,104 @@ func NewWriter(tpduGenerator *AlphaGenerator, messageCodec spi.MessageCodec, tm
 	}
 }
 
-func (m Writer) Write(ctx context.Context, writeRequest model.PlcWriteRequest) <-chan model.PlcWriteRequestResult {
-	// TODO: handle context
-	result := make(chan model.PlcWriteRequestResult)
+func (m Writer) Write(ctx context.Context, writeRequest apiModel.PlcWriteRequest) <-chan apiModel.PlcWriteRequestResult {
+	log.Trace().Msg("Writing")
+	result := make(chan apiModel.PlcWriteRequestResult)
 	go func() {
-		result <- &plc4goModel.DefaultPlcWriteRequestResult{
+		numFields := len(writeRequest.GetFieldNames())
+		if numFields > 20 { // letters g-z
+			result <- &spiModel.DefaultPlcWriteRequestResult{
+				Request:  writeRequest,
+				Response: nil,
+				Err:      errors.New("Only 20 fields can be handled at once"),
+			}
+			return
+		}
+
+		messages := make(map[string]readWriteModel.CBusMessage)
+		for _, fieldName := range writeRequest.GetFieldNames() {
+			field := writeRequest.GetField(fieldName)
+			plcValue := writeRequest.GetValue(fieldName)
+			message, _, supportsWrite, _, err := FieldToCBusMessage(field, plcValue, m.alphaGenerator, m.messageCodec.(*MessageCodec))
+			if !supportsWrite {
+				result <- &spiModel.DefaultPlcWriteRequestResult{
+					Request:  writeRequest,
+					Response: nil,
+					Err:      errors.Wrapf(err, "Error encoding cbus message for field %s. Field is not meant to be written.", fieldName),
+				}
+				return
+			}
+			if err != nil {
+				result <- &spiModel.DefaultPlcWriteRequestResult{
+					Request:  writeRequest,
+					Response: nil,
+					Err:      errors.Wrapf(err, "Error encoding cbus message for field %s", fieldName),
+				}
+				return
+			}
+			messages[fieldName] = message
+		}
+		responseMu := sync.Mutex{}
+		responseCodes := map[string]apiModel.PlcResponseCode{}
+		addResponseCode := func(name string, responseCode apiModel.PlcResponseCode) {
+			responseMu.Lock()
+			defer responseMu.Unlock()
+			responseCodes[name] = responseCode
+		}
+		for fieldName, messageToSend := range messages {
+			if err := ctx.Err(); err != nil {
+				result <- &spiModel.DefaultPlcWriteRequestResult{
+					Request: writeRequest,
+					Err:     err,
+				}
+				return
+			}
+			fieldNameCopy := fieldName
+			// Start a new request-transaction (Is ended in the response-handler)
+			transaction := m.tm.StartTransaction()
+			transaction.Submit(func() {
+				// Send the  over the wire
+				log.Trace().Msg("Send ")
+				if err := m.messageCodec.SendRequest(ctx, messageToSend, func(receivedMessage spi.Message) bool {
+					cbusMessage, ok := receivedMessage.(readWriteModel.CBusMessageExactly)
+					if !ok {
+						return false
+					}
+					messageToClient, ok := cbusMessage.(readWriteModel.CBusMessageToClientExactly)
+					if !ok {
+						return false
+					}
+					// Check if this errored
+					if _, ok = messageToClient.GetReply().(readWriteModel.ServerErrorReplyExactly); ok {
+						// This means we must handle this below
+						return true
+					}
+
+					confirmation, ok := messageToClient.GetReply().(readWriteModel.ReplyOrConfirmationConfirmationExactly)
+					if !ok {
+						return false
+					}
+					return confirmation.GetConfirmation().GetAlpha().GetCharacter() == messageToSend.(readWriteModel.CBusMessageToServer).GetRequest().(readWriteModel.RequestCommand).GetAlpha().GetCharacter()
+				}, func(receivedMessage spi.Message) error {
+					// Convert the response into an
+					addResponseCode(fieldName, apiModel.PlcResponseCode_OK)
+					return transaction.EndRequest()
+				}, func(err error) error {
+					log.Debug().Msgf("Error waiting for field %s", fieldNameCopy)
+					addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_REQUEST_TIMEOUT)
+					// TODO: ok or not ok?
+					return transaction.EndRequest()
+				}, time.Second*1); err != nil {
+					log.Debug().Err(err).Msgf("Error sending message for field %s", fieldNameCopy)
+					addResponseCode(fieldNameCopy, apiModel.PlcResponseCode_INTERNAL_ERROR)
+					_ = transaction.EndRequest()
+				}
+			})
+		}
+		readResponse := spiModel.NewDefaultPlcWriteResponse(writeRequest, responseCodes)
+		result <- &spiModel.DefaultPlcWriteRequestResult{
 			Request:  writeRequest,
-			Response: nil,
-			Err:      errors.New("Not yet implemented"),
+			Response: readResponse,
 		}
 	}()
 	return result


[plc4x] 01/02: feat(plc4go): Treat a single element PlcValueList as PlcValue

Posted by sr...@apache.org.
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 88de0758a4ba877094756d7e76fa4fec2fc460e3
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Thu Aug 18 12:36:24 2022 +0200

    feat(plc4go): Treat a single element PlcValueList as PlcValue
---
 plc4go/spi/values/PlcList.go | 156 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 145 insertions(+), 11 deletions(-)

diff --git a/plc4go/spi/values/PlcList.go b/plc4go/spi/values/PlcList.go
index 5414bf310..a7a3127b7 100644
--- a/plc4go/spi/values/PlcList.go
+++ b/plc4go/spi/values/PlcList.go
@@ -23,7 +23,9 @@ import (
 	"github.com/apache/plc4x/plc4go/pkg/api/values"
 	"github.com/apache/plc4x/plc4go/spi/utils"
 	"github.com/pkg/errors"
+	"github.com/rs/zerolog/log"
 	"strings"
+	"time"
 )
 
 type PlcList struct {
@@ -37,36 +39,168 @@ func NewPlcList(values []values.PlcValue) values.PlcValue {
 	}
 }
 
+func singleOrAdapter[R any](plcList PlcList, f func(values.PlcValue) R) R {
+	if len(plcList.Values) == 1 {
+		return f(plcList.Values[0])
+	} else {
+		return f(plcList.PlcValueAdapter)
+	}
+}
+
+////
+// Simple Types
+
+func (m PlcList) IsSimple() bool   { return singleOrAdapter(m, values.PlcValue.IsSimple) }
+func (m PlcList) IsNullable() bool { return singleOrAdapter(m, values.PlcValue.IsNullable) }
+func (m PlcList) IsNull() bool     { return singleOrAdapter(m, values.PlcValue.IsNull) }
+
+//
+///
+
+////
+// Boolean
+
+func (m PlcList) IsBool() bool          { return singleOrAdapter(m, values.PlcValue.IsBool) }
+func (m PlcList) GetBoolLength() uint32 { return singleOrAdapter(m, values.PlcValue.GetBoolLength) }
+func (m PlcList) GetBool() bool         { return singleOrAdapter(m, values.PlcValue.GetBool) }
+func (m PlcList) GetBoolAt(index uint32) bool {
+	return m.PlcValueAdapter.GetBoolAt(index)
+}
+func (m PlcList) GetBoolArray() []bool { return singleOrAdapter(m, values.PlcValue.GetBoolArray) }
+
+//
+///
+
+////
+// Byte
+
+func (m PlcList) IsByte() bool  { return singleOrAdapter(m, values.PlcValue.IsByte) }
+func (m PlcList) GetByte() byte { return singleOrAdapter(m, values.PlcValue.GetByte) }
+
+//
+///
+
+////
+// Integer
+
+func (m PlcList) IsUint8() bool     { return singleOrAdapter(m, values.PlcValue.IsUint8) }
+func (m PlcList) GetUint8() uint8   { return singleOrAdapter(m, values.PlcValue.GetUint8) }
+func (m PlcList) IsUint16() bool    { return singleOrAdapter(m, values.PlcValue.IsUint16) }
+func (m PlcList) GetUint16() uint16 { return singleOrAdapter(m, values.PlcValue.GetUint16) }
+func (m PlcList) IsUint32() bool    { return singleOrAdapter(m, values.PlcValue.IsUint32) }
+func (m PlcList) GetUint32() uint32 { return singleOrAdapter(m, values.PlcValue.GetUint32) }
+func (m PlcList) IsUint64() bool    { return singleOrAdapter(m, values.PlcValue.IsUint64) }
+func (m PlcList) GetUint64() uint64 { return singleOrAdapter(m, values.PlcValue.GetUint64) }
+func (m PlcList) IsInt8() bool      { return singleOrAdapter(m, values.PlcValue.IsInt8) }
+func (m PlcList) GetInt8() int8     { return singleOrAdapter(m, values.PlcValue.GetInt8) }
+func (m PlcList) IsInt16() bool     { return singleOrAdapter(m, values.PlcValue.IsInt16) }
+func (m PlcList) GetInt16() int16   { return singleOrAdapter(m, values.PlcValue.GetInt16) }
+func (m PlcList) IsInt32() bool     { return singleOrAdapter(m, values.PlcValue.IsInt32) }
+func (m PlcList) GetInt32() int32   { return singleOrAdapter(m, values.PlcValue.GetInt32) }
+func (m PlcList) IsInt64() bool     { return singleOrAdapter(m, values.PlcValue.IsInt64) }
+func (m PlcList) GetInt64() int64   { return singleOrAdapter(m, values.PlcValue.GetInt64) }
+
+//
+///
+
+////
+// Floating Point
+
+func (m PlcList) IsFloat32() bool     { return singleOrAdapter(m, values.PlcValue.IsFloat32) }
+func (m PlcList) GetFloat32() float32 { return singleOrAdapter(m, values.PlcValue.GetFloat32) }
+func (m PlcList) IsFloat64() bool     { return singleOrAdapter(m, values.PlcValue.IsFloat64) }
+func (m PlcList) GetFloat64() float64 { return singleOrAdapter(m, values.PlcValue.GetFloat64) }
+
+//
+///
+
+////
+// String
+
+func (m PlcList) IsString() bool { return singleOrAdapter(m, values.PlcValue.IsString) }
+func (m PlcList) GetString() string {
+	stringValues := make([]string, len(m.Values))
+	for i, v := range m.Values {
+		stringValues[i] = v.GetString()
+	}
+	return strings.Join(stringValues, ", ")
+}
+
+//
+///
+
+////
+// Time
+
+func (m PlcList) IsTime() bool           { return singleOrAdapter(m, values.PlcValue.IsTime) }
+func (m PlcList) GetTime() time.Time     { return singleOrAdapter(m, values.PlcValue.GetTime) }
+func (m PlcList) IsDate() bool           { return singleOrAdapter(m, values.PlcValue.IsDate) }
+func (m PlcList) GetDate() time.Time     { return singleOrAdapter(m, values.PlcValue.GetDate) }
+func (m PlcList) IsDateTime() bool       { return singleOrAdapter(m, values.PlcValue.IsDateTime) }
+func (m PlcList) GetDateTime() time.Time { return singleOrAdapter(m, values.PlcValue.GetDateTime) }
+
+//
+///
+
+////
+// Raw Access
+
 func (m PlcList) GetRaw() []byte {
 	buf := utils.NewWriteBufferByteBased()
-	m.Serialize(buf)
+	if err := m.Serialize(buf); err != nil {
+		log.Error().Err(err).Msg("Error getting raw")
+		return nil
+	}
 	return buf.GetBytes()
 }
 
-func (m PlcList) IsList() bool {
-	return true
-}
+//
+///
 
+////
+// List Methods
+
+func (m PlcList) IsList() bool { return true }
 func (m PlcList) GetLength() uint32 {
 	return uint32(len(m.Values))
 }
-
 func (m PlcList) GetIndex(i uint32) values.PlcValue {
 	return m.Values[i]
 }
-
 func (m PlcList) GetList() []values.PlcValue {
 	return m.Values
 }
 
-func (m PlcList) GetString() string {
-	stringValues := make([]string, len(m.Values))
-	for i, v := range m.Values {
-		stringValues[i] = v.GetString()
+//
+///
+
+////
+// Struct Methods
+
+func (m PlcList) IsStruct() bool    { return singleOrAdapter(m, values.PlcValue.IsStruct) }
+func (m PlcList) GetKeys() []string { return singleOrAdapter(m, values.PlcValue.GetKeys) }
+func (m PlcList) HasKey(key string) bool {
+	if len(m.Values) == 1 {
+		return m.Values[0].HasKey(key)
+	} else {
+		return m.PlcValueAdapter.HasKey(key)
 	}
-	return strings.Join(stringValues, ", ")
+
+}
+func (m PlcList) GetValue(key string) values.PlcValue {
+	if len(m.Values) == 1 {
+		return m.Values[0].GetValue(key)
+	} else {
+		return m.PlcValueAdapter.GetValue(key)
+	}
+}
+func (m PlcList) GetStruct() map[string]values.PlcValue {
+	return singleOrAdapter(m, values.PlcValue.GetStruct)
 }
 
+//
+///
+
 func (m PlcList) Serialize(writeBuffer utils.WriteBuffer) error {
 	if err := writeBuffer.PushContext("PlcList"); err != nil {
 		return err