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 2026/08/18 20:22:24 UTC

(plc4x) 06/17: fix(plc4go/eip): reconcile CIP value codecs with plc4j (arrays, REAL/LREAL/LINT/STRING, short-reply guard)

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 60149e3389d0a480b5fc6c962cf6a194262d7f07
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Tue Aug 18 17:06:47 2026 +0200

    fix(plc4go/eip): reconcile CIP value codecs with plc4j (arrays, REAL/LREAL/LINT/STRING, short-reply guard)
---
 plc4go/internal/eip/Reader.go      | 103 +-------------------
 plc4go/internal/eip/TagHandler.go  |  33 ++++++-
 plc4go/internal/eip/Values.go      | 188 +++++++++++++++++++++++++++++++++++++
 plc4go/internal/eip/Values_test.go | 112 ++++++++++++++++++++++
 plc4go/internal/eip/Writer.go      |  34 +------
 5 files changed, 334 insertions(+), 136 deletions(-)

diff --git a/plc4go/internal/eip/Reader.go b/plc4go/internal/eip/Reader.go
index 339e5b5389..5de3754072 100644
--- a/plc4go/internal/eip/Reader.go
+++ b/plc4go/internal/eip/Reader.go
@@ -40,7 +40,6 @@ import (
 	"github.com/apache/plc4x/plc4go/spi/options"
 	"github.com/apache/plc4x/plc4go/spi/transactions"
 	"github.com/apache/plc4x/plc4go/spi/utils"
-	spiValues "github.com/apache/plc4x/plc4go/spi/values"
 )
 
 type Reader struct {
@@ -220,10 +219,9 @@ func (m *Reader) ToPlc4xReadResponse(response readWriteModel.CipService, readReq
 		code := decodeResponseCode(cipReadResponse.GetStatus())
 		var plcValue values.PlcValue
 		_type := cipReadResponse.GetData().GetDataType()
-		data := utils.NewReadBufferByteBased(cipReadResponse.GetData().GetData(), utils.WithByteOrderForReadBufferByteBased(binary.LittleEndian))
 		if code == apiModel.PlcResponseCode_OK {
 			var err error
-			plcValue, err = parsePlcValue(tag, data, _type)
+			plcValue, err = parsePlcValue(tag, cipReadResponse.GetData().GetData(), _type)
 			if err != nil {
 				return nil, err
 			}
@@ -258,11 +256,10 @@ func (m *Reader) ToPlc4xReadResponse(response readWriteModel.CipService, readReq
 			if cipReadResponse, ok := services.Services[i].(readWriteModel.CipReadResponse); ok {
 				code := decodeResponseCode(cipReadResponse.GetStatus())
 				_type := cipReadResponse.GetData().GetDataType()
-				data := utils.NewReadBufferByteBased(cipReadResponse.GetData().GetData(), utils.WithByteOrderForReadBufferByteBased(binary.LittleEndian))
 				var plcValue values.PlcValue
 				if code == apiModel.PlcResponseCode_OK {
 					var err error
-					plcValue, err = parsePlcValue(tag, data, _type)
+					plcValue, err = parsePlcValue(tag, cipReadResponse.GetData().GetData(), _type)
 					if err != nil {
 						return nil, err
 					}
@@ -282,99 +279,3 @@ func (m *Reader) ToPlc4xReadResponse(response readWriteModel.CipService, readReq
 	m.log.Trace().Msg("Returning the response")
 	return spiModel.NewDefaultPlcReadResponse(readRequest, responseCodes, plcValues), nil
 }
-
-func parsePlcValue(tag PlcTag, data utils.ReadBufferByteBased, _type readWriteModel.CIPDataTypeCode) (values.PlcValue, error) {
-	nb := tag.GetElementNb()
-	if nb > 1 {
-		list := make([]values.PlcValue, 0)
-		for range nb {
-			switch _type {
-			case readWriteModel.CIPDataTypeCode_DINT:
-				readInt32, err := data.ReadInt32("", _type.Size()*8)
-				if err != nil {
-					return nil, err
-				}
-				list = append(list, spiValues.NewPlcDINT(readInt32))
-			case readWriteModel.CIPDataTypeCode_INT:
-				readInt16, err := data.ReadInt16("", _type.Size()*8)
-				if err != nil {
-					return nil, err
-				}
-				list = append(list, spiValues.NewPlcINT(readInt16))
-			case readWriteModel.CIPDataTypeCode_SINT:
-				readInt8, err := data.ReadInt8("", _type.Size()*8)
-				if err != nil {
-					return nil, err
-				}
-				list = append(list, spiValues.NewPlcSINT(readInt8))
-			case readWriteModel.CIPDataTypeCode_REAL:
-				if _type.Size()*8 != 64 {
-					return nil, errors.New("Unexpected size")
-				}
-				readFloat64, err := data.ReadFloat64("", 64)
-				if err != nil {
-					return nil, err
-				}
-				list = append(list, spiValues.NewPlcLREAL(readFloat64))
-			case readWriteModel.CIPDataTypeCode_BOOL:
-				bit, err := data.ReadBit("")
-				if err != nil {
-					return nil, err
-				}
-				list = append(list, spiValues.NewPlcBOOL(bit))
-			default:
-				return nil, errors.Errorf("Unknown type %v", _type)
-			}
-		}
-		return spiValues.NewPlcList(list), nil
-	} else {
-		switch _type {
-		case readWriteModel.CIPDataTypeCode_SINT:
-			readByte, err := data.ReadInt8("", _type.Size()*8)
-			if err != nil {
-				return nil, err
-			}
-			return spiValues.NewPlcSINT(readByte), nil
-		case readWriteModel.CIPDataTypeCode_INT:
-			readInt16, err := data.ReadInt16("", _type.Size()*8)
-			if err != nil {
-				return nil, err
-			}
-			return spiValues.NewPlcINT(readInt16), nil
-		case readWriteModel.CIPDataTypeCode_DINT:
-			readInt32, err := data.ReadInt32("", _type.Size()*8)
-			if err != nil {
-				return nil, err
-			}
-			return spiValues.NewPlcDINT(readInt32), nil
-		case readWriteModel.CIPDataTypeCode_REAL:
-			if _type.Size()*8 != 64 {
-				return nil, errors.New("Unexpected size")
-			}
-			readFloat32, err := data.ReadFloat32("", 64)
-			if err != nil {
-				return nil, err
-			}
-			return spiValues.NewPlcREAL(readFloat32), nil
-		case readWriteModel.CIPDataTypeCode_BOOL:
-			readBit, err := data.ReadBit("")
-			if err != nil {
-				return nil, err
-			}
-			return spiValues.NewPlcBOOL(readBit), nil
-		default:
-			return nil, errors.Errorf("Unknown type %v", _type)
-		}
-	}
-}
-
-// Helper to convert the return codes returned from the eip into one of our standard
-func decodeResponseCode(status uint8) apiModel.PlcResponseCode {
-	//TODO other status
-	switch status {
-	case 0:
-		return apiModel.PlcResponseCode_OK
-	default:
-		return apiModel.PlcResponseCode_INTERNAL_ERROR
-	}
-}
diff --git a/plc4go/internal/eip/TagHandler.go b/plc4go/internal/eip/TagHandler.go
index 3013f07013..9b5bb999d9 100644
--- a/plc4go/internal/eip/TagHandler.go
+++ b/plc4go/internal/eip/TagHandler.go
@@ -22,6 +22,7 @@ package eip
 import (
 	"fmt"
 	"regexp"
+	"strconv"
 
 	apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
 	"github.com/apache/plc4x/plc4go/protocols/eip/readwrite/model"
@@ -44,8 +45,36 @@ const (
 )
 
 func (m TagHandler) ParseTag(tagAddress string) (apiModel.PlcTag, error) {
-	// TODO: This isn't pretty ...
-	return NewTag(tagAddress, model.CIPDataTypeCode_DINT, uint16(1)), nil
+	matches := m.addressPattern.FindStringSubmatch(tagAddress)
+	if matches == nil {
+		return nil, fmt.Errorf("invalid tag address: %s", tagAddress)
+	}
+
+	tagName := matches[m.addressPattern.SubexpIndex(TAG)]
+	dataTypeStr := matches[m.addressPattern.SubexpIndex(DATA_TYPE)]
+	elementNbStr := matches[m.addressPattern.SubexpIndex(ELEMENT_NB)]
+
+	var dataType model.CIPDataTypeCode
+	if dataTypeStr == "" {
+		dataType = model.CIPDataTypeCode_DINT
+	} else {
+		var found bool
+		dataType, found = model.CIPDataTypeCodeByName(dataTypeStr)
+		if !found {
+			return nil, fmt.Errorf("unknown data type: %s", dataTypeStr)
+		}
+	}
+
+	elementNb := uint16(1)
+	if elementNbStr != "" {
+		nb, err := strconv.ParseUint(elementNbStr, 10, 16)
+		if err != nil {
+			return nil, fmt.Errorf("invalid element count: %s", elementNbStr)
+		}
+		elementNb = uint16(nb)
+	}
+
+	return NewTag(tagName, dataType, elementNb), nil
 }
 
 func (m TagHandler) ParseQuery(query string) (apiModel.PlcQuery, error) {
diff --git a/plc4go/internal/eip/Values.go b/plc4go/internal/eip/Values.go
new file mode 100644
index 0000000000..267f4fb1ee
--- /dev/null
+++ b/plc4go/internal/eip/Values.go
@@ -0,0 +1,188 @@
+/*
+ * 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 eip
+
+import (
+	"encoding/binary"
+	"math"
+
+	apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
+	"github.com/apache/plc4x/plc4go/pkg/api/values"
+	apiValues "github.com/apache/plc4x/plc4go/pkg/api/values"
+	readWriteModel "github.com/apache/plc4x/plc4go/protocols/eip/readwrite/model"
+	"github.com/apache/plc4x/plc4go/spi/errors"
+	spiValues "github.com/apache/plc4x/plc4go/spi/values"
+)
+
+const (
+	stringLenOffset  = 2
+	stringDataOffset = 6
+)
+
+// elementCount is the number of elements to ask the device for. An array tag
+// has to request all of its elements (GH-1008) - otherwise the device returns
+// a single element and the decoder has nothing to read the rest from.
+func elementCount(tag PlcTag) uint16 {
+	if tag.GetElementNb() > 1 {
+		return tag.GetElementNb()
+	}
+	return 1
+}
+
+// isFixedSize reports whether the type is stored as a flat sequence of equally
+// sized elements.
+func isFixedSize(dataType readWriteModel.CIPDataTypeCode) bool {
+	switch dataType {
+	case readWriteModel.CIPDataTypeCode_SINT,
+		readWriteModel.CIPDataTypeCode_INT,
+		readWriteModel.CIPDataTypeCode_DINT,
+		readWriteModel.CIPDataTypeCode_LINT,
+		readWriteModel.CIPDataTypeCode_REAL,
+		readWriteModel.CIPDataTypeCode_LREAL,
+		readWriteModel.CIPDataTypeCode_BOOL:
+		return true
+	default:
+		return false
+	}
+}
+
+// parsePlcValue decodes a CIP read payload. CIP data is little-endian on the
+// wire regardless of the encapsulation byte order.
+func parsePlcValue(tag PlcTag, rawData []byte, dataType readWriteModel.CIPDataTypeCode) (values.PlcValue, error) {
+	nb := int(elementCount(tag))
+	elementSize := int(dataType.Size())
+	if isFixedSize(dataType) && len(rawData) < nb*elementSize {
+		// Never read past what the device actually sent - a short reply must not
+		// blow up the whole response.
+		return nil, errors.Errorf("device returned %d bytes for tag '%s', expected %d for %d element(s) of %s",
+			len(rawData), tag.GetTag(), nb*elementSize, nb, dataType)
+	}
+	if nb > 1 {
+		list := make([]values.PlcValue, 0, nb)
+		for i := 0; i < nb; i++ {
+			offset := i * elementSize
+			switch dataType {
+			case readWriteModel.CIPDataTypeCode_SINT:
+				list = append(list, spiValues.NewPlcSINT(int8(rawData[offset])))
+			case readWriteModel.CIPDataTypeCode_INT:
+				list = append(list, spiValues.NewPlcINT(int16(binary.LittleEndian.Uint16(rawData[offset:]))))
+			case readWriteModel.CIPDataTypeCode_DINT:
+				list = append(list, spiValues.NewPlcDINT(int32(binary.LittleEndian.Uint32(rawData[offset:]))))
+			case readWriteModel.CIPDataTypeCode_LINT:
+				list = append(list, spiValues.NewPlcLINT(int64(binary.LittleEndian.Uint64(rawData[offset:]))))
+			case readWriteModel.CIPDataTypeCode_REAL:
+				list = append(list, spiValues.NewPlcREAL(math.Float32frombits(binary.LittleEndian.Uint32(rawData[offset:]))))
+			case readWriteModel.CIPDataTypeCode_LREAL:
+				list = append(list, spiValues.NewPlcLREAL(math.Float64frombits(binary.LittleEndian.Uint64(rawData[offset:]))))
+			case readWriteModel.CIPDataTypeCode_BOOL:
+				list = append(list, spiValues.NewPlcBOOL(rawData[offset] != 0))
+			case readWriteModel.CIPDataTypeCode_STRING, readWriteModel.CIPDataTypeCode_STRUCTURED:
+				// STRING/STRUCTURED carry their own length; plc4j decodes a single
+				// string here and returns.
+				value, err := parseStructured(tag, rawData)
+				if err != nil {
+					return nil, err
+				}
+				list = append(list, value)
+				return spiValues.NewPlcList(list), nil
+			default:
+				return nil, errors.Errorf("unsupported type %s", dataType)
+			}
+		}
+		return spiValues.NewPlcList(list), nil
+	}
+	switch dataType {
+	case readWriteModel.CIPDataTypeCode_SINT:
+		return spiValues.NewPlcSINT(int8(rawData[0])), nil
+	case readWriteModel.CIPDataTypeCode_INT:
+		return spiValues.NewPlcINT(int16(binary.LittleEndian.Uint16(rawData))), nil
+	case readWriteModel.CIPDataTypeCode_DINT:
+		return spiValues.NewPlcDINT(int32(binary.LittleEndian.Uint32(rawData))), nil
+	case readWriteModel.CIPDataTypeCode_LINT:
+		return spiValues.NewPlcLINT(int64(binary.LittleEndian.Uint64(rawData))), nil
+	case readWriteModel.CIPDataTypeCode_REAL:
+		return spiValues.NewPlcREAL(math.Float32frombits(binary.LittleEndian.Uint32(rawData))), nil
+	case readWriteModel.CIPDataTypeCode_LREAL:
+		return spiValues.NewPlcLREAL(math.Float64frombits(binary.LittleEndian.Uint64(rawData))), nil
+	case readWriteModel.CIPDataTypeCode_BOOL:
+		return spiValues.NewPlcBOOL(rawData[0] != 0), nil
+	case readWriteModel.CIPDataTypeCode_STRING, readWriteModel.CIPDataTypeCode_STRUCTURED:
+		return parseStructured(tag, rawData)
+	default:
+		return nil, errors.Errorf("unsupported type %s", dataType)
+	}
+}
+
+func parseStructured(tag PlcTag, rawData []byte) (values.PlcValue, error) {
+	if len(rawData) < stringDataOffset {
+		return nil, errors.Errorf("structured payload for tag '%s' too short: %d bytes", tag.GetTag(), len(rawData))
+	}
+	structuredType := binary.LittleEndian.Uint16(rawData[0:])
+	structuredLen := int(binary.LittleEndian.Uint16(rawData[stringLenOffset:]))
+	if structuredType != uint16(readWriteModel.CIPStructTypeCode_STRING) {
+		return nil, errors.Errorf("unsupported structured type %#x for tag '%s'", structuredType, tag.GetTag())
+	}
+	if len(rawData) < stringDataOffset+structuredLen {
+		return nil, errors.Errorf("string payload for tag '%s' too short: %d bytes for length %d",
+			tag.GetTag(), len(rawData), structuredLen)
+	}
+	return spiValues.NewPlcSTRING(string(rawData[stringDataOffset : stringDataOffset+structuredLen])), nil
+}
+
+// encodeValue serializes a single value for a CIP write. Like plc4j it encodes
+// one element; array writes carry the element count in the CipWriteRequest.
+func encodeValue(value apiValues.PlcValue, dataType readWriteModel.CIPDataTypeCode) ([]byte, error) {
+	switch dataType {
+	case readWriteModel.CIPDataTypeCode_BOOL:
+		boolByte := byte(0)
+		if value.GetBool() {
+			boolByte = 1
+		}
+		return []byte{boolByte}, nil
+	case readWriteModel.CIPDataTypeCode_SINT:
+		return []byte{byte(value.GetInt8())}, nil
+	case readWriteModel.CIPDataTypeCode_INT:
+		return binary.LittleEndian.AppendUint16(nil, uint16(value.GetInt16())), nil
+	case readWriteModel.CIPDataTypeCode_DINT:
+		return binary.LittleEndian.AppendUint32(nil, uint32(value.GetInt32())), nil
+	case readWriteModel.CIPDataTypeCode_LINT:
+		return binary.LittleEndian.AppendUint64(nil, uint64(value.GetInt64())), nil
+	case readWriteModel.CIPDataTypeCode_REAL:
+		return binary.LittleEndian.AppendUint32(nil, math.Float32bits(value.GetFloat32())), nil
+	case readWriteModel.CIPDataTypeCode_LREAL:
+		return binary.LittleEndian.AppendUint64(nil, math.Float64bits(value.GetFloat64())), nil
+	case readWriteModel.CIPDataTypeCode_STRING, readWriteModel.CIPDataTypeCode_STRUCTURED:
+		text := value.GetString()
+		raw := binary.LittleEndian.AppendUint32(nil, uint32(len(text)))
+		return append(raw, text...), nil
+	default:
+		return nil, errors.Errorf("unsupported type %s", dataType)
+	}
+}
+
+// decodeResponseCode converts a CIP status into a PLC4X response code.
+func decodeResponseCode(status uint8) apiModel.PlcResponseCode {
+	switch status {
+	case 0:
+		return apiModel.PlcResponseCode_OK
+	default:
+		return apiModel.PlcResponseCode_INTERNAL_ERROR
+	}
+}
diff --git a/plc4go/internal/eip/Values_test.go b/plc4go/internal/eip/Values_test.go
new file mode 100644
index 0000000000..8d2e3f1307
--- /dev/null
+++ b/plc4go/internal/eip/Values_test.go
@@ -0,0 +1,112 @@
+/*
+ * 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 eip
+
+import (
+	"encoding/binary"
+	"math"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+
+	apiModel "github.com/apache/plc4x/plc4go/pkg/api/model"
+	readWriteModel "github.com/apache/plc4x/plc4go/protocols/eip/readwrite/model"
+	spiValues "github.com/apache/plc4x/plc4go/spi/values"
+)
+
+func mustTag(t *testing.T, address string) PlcTag {
+	tag, err := NewTagHandler().ParseTag(address)
+	require.NoError(t, err)
+	return tag.(PlcTag)
+}
+
+func TestParsePlcValueSingleDINT(t *testing.T) {
+	value, err := parsePlcValue(mustTag(t, "%rate:DINT"), []byte{0x16, 0x02, 0x00, 0x00}, readWriteModel.CIPDataTypeCode_DINT)
+	require.NoError(t, err)
+	assert.Equal(t, int32(0x216), value.GetInt32())
+}
+
+func TestParsePlcValueSingleREAL(t *testing.T) {
+	raw := binary.LittleEndian.AppendUint32(nil, math.Float32bits(1.5))
+	value, err := parsePlcValue(mustTag(t, "%f:REAL"), raw, readWriteModel.CIPDataTypeCode_REAL)
+	require.NoError(t, err)
+	assert.Equal(t, float32(1.5), value.GetFloat32())
+}
+
+func TestParsePlcValueSingleBOOL(t *testing.T) {
+	value, err := parsePlcValue(mustTag(t, "%b:BOOL"), []byte{0x01}, readWriteModel.CIPDataTypeCode_BOOL)
+	require.NoError(t, err)
+	assert.True(t, value.GetBool())
+}
+
+func TestParsePlcValueDINTArray(t *testing.T) {
+	raw := []byte{0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00}
+	value, err := parsePlcValue(mustTag(t, "%arr[0]:DINT:2"), raw, readWriteModel.CIPDataTypeCode_DINT)
+	require.NoError(t, err)
+	require.True(t, value.IsList())
+	list := value.GetList()
+	require.Len(t, list, 2)
+	assert.Equal(t, int32(1), list[0].GetInt32())
+	assert.Equal(t, int32(2), list[1].GetInt32())
+}
+
+func TestParsePlcValueShortReplyIsError(t *testing.T) {
+	// 2 DINT elements requested but only 4 bytes returned: must error, not panic (GH-954 thread)
+	_, err := parsePlcValue(mustTag(t, "%arr[0]:DINT:2"), []byte{0x01, 0x00, 0x00, 0x00}, readWriteModel.CIPDataTypeCode_DINT)
+	require.Error(t, err)
+}
+
+func TestParsePlcValueString(t *testing.T) {
+	// structured header: type 0x0FCE (STRING), len 2, 2 pad bytes, then "AB"
+	raw := []byte{0xCE, 0x0F, 0x02, 0x00, 0x00, 0x00, 0x41, 0x42}
+	value, err := parsePlcValue(mustTag(t, "%s:STRING"), raw, readWriteModel.CIPDataTypeCode_STRING)
+	require.NoError(t, err)
+	assert.Equal(t, "AB", value.GetString())
+}
+
+func TestEncodeValueDINT(t *testing.T) {
+	raw, err := encodeValue(spiValues.NewPlcDINT(0x216), readWriteModel.CIPDataTypeCode_DINT)
+	require.NoError(t, err)
+	assert.Equal(t, []byte{0x16, 0x02, 0x00, 0x00}, raw)
+}
+
+func TestEncodeValueREAL(t *testing.T) {
+	raw, err := encodeValue(spiValues.NewPlcREAL(1.0), readWriteModel.CIPDataTypeCode_REAL)
+	require.NoError(t, err)
+	assert.Equal(t, []byte{0x00, 0x00, 0x80, 0x3F}, raw)
+}
+
+func TestEncodeValueBOOL(t *testing.T) {
+	raw, err := encodeValue(spiValues.NewPlcBOOL(true), readWriteModel.CIPDataTypeCode_BOOL)
+	require.NoError(t, err)
+	assert.Equal(t, []byte{0x01}, raw)
+}
+
+func TestEncodeValueSTRING(t *testing.T) {
+	raw, err := encodeValue(spiValues.NewPlcSTRING("AB"), readWriteModel.CIPDataTypeCode_STRING)
+	require.NoError(t, err)
+	assert.Equal(t, []byte{0x02, 0x00, 0x00, 0x00, 0x41, 0x42}, raw)
+}
+
+func TestDecodeResponseCode(t *testing.T) {
+	assert.Equal(t, apiModel.PlcResponseCode_OK, decodeResponseCode(0))
+	assert.Equal(t, apiModel.PlcResponseCode_INTERNAL_ERROR, decodeResponseCode(5))
+}
diff --git a/plc4go/internal/eip/Writer.go b/plc4go/internal/eip/Writer.go
index 923945293e..27abeedec6 100644
--- a/plc4go/internal/eip/Writer.go
+++ b/plc4go/internal/eip/Writer.go
@@ -21,7 +21,6 @@ package eip
 
 import (
 	"context"
-	"encoding/binary"
 	"runtime/debug"
 	"strings"
 	"sync"
@@ -29,7 +28,6 @@ import (
 	"github.com/rs/zerolog"
 
 	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/eip/readwrite/model"
 	"github.com/apache/plc4x/plc4go/spi"
 	"github.com/apache/plc4x/plc4go/spi/errors"
@@ -91,7 +89,7 @@ func (m *Writer) Write(ctx context.Context, writeRequest apiModel.PlcWriteReques
 			if isArray {
 				dataLength += 2
 			}
-			data, err := encodeValue(value, eipTag.GetType(), elements)
+			data, err := encodeValue(value, eipTag.GetType())
 			if err != nil {
 				utils.DeliverResult(m.log, result, spiModel.NewDefaultPlcWriteRequestResult(writeRequest, nil, errors.Wrapf(err, "Error encoding value for eipTag %s", tagName)))
 				return
@@ -283,36 +281,6 @@ func (m *Writer) Write(ctx context.Context, writeRequest apiModel.PlcWriteReques
 	return result
 }
 
-func encodeValue(value apiValues.PlcValue, _type readWriteModel.CIPDataTypeCode, elements uint16) ([]byte, error) {
-	buffer := utils.NewWriteBufferByteBased(utils.WithByteOrderForByteBasedBuffer(binary.LittleEndian))
-	switch _type {
-	case readWriteModel.CIPDataTypeCode_SINT:
-		err := buffer.WriteByte("", value.GetUint8())
-		if err != nil {
-			return nil, err
-		}
-	case readWriteModel.CIPDataTypeCode_INT:
-		err := buffer.WriteInt16("", 16, value.GetInt16())
-		if err != nil {
-			return nil, err
-		}
-	case readWriteModel.CIPDataTypeCode_DINT:
-		err := buffer.WriteInt32("", 32, value.GetInt32())
-		if err != nil {
-			return nil, err
-		}
-	case readWriteModel.CIPDataTypeCode_REAL:
-		err := buffer.WriteFloat64("", 64, value.GetFloat64())
-		if err != nil {
-			return nil, err
-		}
-	default:
-		// TODO: what is the default type? write nothing?
-		//panic("unmapped type: " + strconv.Itoa(int(_type)))
-	}
-	return buffer.GetBytes(), nil
-}
-
 func (m *Writer) ToPlc4xWriteResponse(response readWriteModel.CipService, writeRequest apiModel.PlcWriteRequest) (apiModel.PlcWriteResponse, error) {
 	responseCodes := map[string]apiModel.PlcResponseCode{}
 	switch response := response.(type) {