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:23 UTC

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

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