There are significant limitations when writing STRING tags and UDT array element members from external devices via CIP (EtherNet/IP) to Allen-Bradley ControlLogix and CompactLogix PLCs.
This is documented in Rockwell's official publication 1756-PM020 (Logix 5000 Controllers Data Access) and is a fundamental limitation of the CIP protocol implementation for Logix controllers.
These limitations affect all EtherNet/IP communication libraries, including this Rust EtherNet/IP library. They are not bugs in the library implementation, but rather restrictions imposed by the PLC firmware itself.
The STRING data type in Logix 5000 controllers is a Predefined Structure, not an atomic type. It consists of:
LEN(DINT) - The length of the stringDATA[82](SINT array) - The character data (default 82 characters)
Key Limitation from Rockwell Documentation (1756-PM020):
"Do not access complete structure tags of these types [Predefined, Module-Defined, Add-On-Defined], or complete UDTs with nested tags of these types. Instead, access atomic members of these tags."
Writing arrays of User-Defined Types (UDTs) requires:
- Correct Structure Handle (calculated CRC value)
- Proper data packing with padding bytes
- Matching Tag Type Service Parameter
The Write Tag Service returns error 0xFF / 0x2107 ("Tag type used in request does not match the target tag's data type") when the Structure Handle doesn't match.
"Predefined, Module-Defined, and Add-On-Defined structure tags have a more complex set of rules than user-defined data types (UDT). Do not access complete structure tags of these types, or complete UDTs with nested tags of these types."
"The STRING data type is a form of Predefined structure that would normally be excluded after executing steps that are described later."
"IMPORTANT: Reading a structure before writing to it is one way to obtain the value for this parameter [Structure Handle], but that does not provide any understanding of the structure makeup, which is critical information when manipulating structure data. Also, the Structure Handle value is not unique among structures."
"The correct way to access structures as a whole is to first read their template information and understand the data packing."
"General Error: Tag type used in request does not match the target tag's data type."
Option 1: Write .DATA and .LEN separately (Not Currently Supported)
Note: This library does not currently support writing to
.DATAand.LENmembers separately due to the same underlying PLC firmware restrictions. Attempting to write toMyString.DATAorMyString.LENdirectly will also fail with CIP Error 0x2107.
The recommended approach documented by other vendors is to write .DATA and .LEN separately, but this also fails with the same PLC firmware limitation in practice.
// This approach does NOT work with current PLC firmware:
1. Write to MyString.DATA[0] through MyString.DATA[4] = 'H','e','l','l','o' // ❌ Fails
2. Write to MyString.LEN = 5 // ❌ Fails
Option 2: Use LogixString Helper Class (Recommended for This Library)
This library provides a LogixString helper class that mirrors the STRING structure:
// C# Example
using RustEtherNetIp;
var logixString = new LogixString();
logixString.SetString("Hello");
// Write as UDT
client.WriteStringAsUdt("gTest_STRING", logixString);The LogixString class mirrors the internal STRING structure:
LogixString:
- len : Int32 (DINT)
- data : byte[] (SINT array, default 82 bytes)
Note: Even with this approach, writing standalone STRING tags may still fail due to PLC firmware limitations. The workaround is most effective when the STRING is part of a larger UDT structure.
Option 3: Use an intermediary tag
- Create an atomic array tag in the PLC (e.g.,
StringBuffer : SINT[84]) - Write to the atomic array from external device
- Use ladder logic (COP instruction) to copy to the STRING tag
Option 1: Write individual array elements (Supported)
This library supports writing entire UDT array elements:
// ✅ This works - writing entire UDT array element
var udtElement = client.ReadUdt("gTestUDT_Array[0]");
// ... modify UDT structure ...
client.WriteUdt("gTestUDT_Array[0]", udtElement);Option 2: Write individual members (Not Supported for Array Elements)
Writing to individual members of UDT array elements is not supported due to PLC firmware limitations:
// ❌ This does NOT work - writing UDT array element member
client.WriteDint("gTestUDT_Array[0].Member1_DINT", 42); // Fails with CIP Error 0x2107Workaround: Read the entire UDT array element, modify the member in memory, then write the entire element back.
Option 2: Use atomic intermediary arrays
- Create atomic arrays in the PLC that mirror your UDT data
- Write to atomic arrays from external device
- Use ladder logic to assemble into UDT array
Option 3: Calculate Structure Handle correctly
If you must write complete UDT arrays:
- Read the Template Object (Class 0x6C) to get the Structure Handle
- Include the correct 4-byte Tag Type Service Parameter:
A0 02 <Handle_Low> <Handle_High> - Pack data according to alignment rules (32-bit boundaries, padding bytes)
See Rockwell document: https://www.rockwellautomation.com/content/dam/rockwell-automation/sites/downloads/pdf/TypeEncode_CIPRW.pdf
Atomic tags: 2 bytes (e.g., 0x00C4 for DINT)
Structure tags: 4 bytes = 0xA0 0x02 + 2-byte Structure Handle
The Structure Handle is a 16-bit CRC value calculated from the structure definition. It can be obtained by:
- Reading Template Instance Attribute 1 (Class 0x6C)
- Calculating it from the type encoding string (see TypeEncode_CIPRW.pdf)
From 1756-PM020:
- SINTs: 8-bit boundary
- INTs: 16-bit boundary
- DINTs, REALs, arrays: 32-bit boundary
- Structures: Begin and end on 32-bit boundaries
- Pad bytes are inserted for alignment and must be sent on the wire
| Error | Extended | Meaning |
|---|---|---|
| 0x04 | 0x0000 | Syntax error decoding Request Path |
| 0x05 | 0x0000 | Request Path destination unknown |
| 0x06 | N/A | Insufficient Packet Space |
| 0xFF | 0x2104 | Offset beyond end of tag |
| 0xFF | 0x2105 | Access beyond end of object |
| 0xFF | 0x2107 | Tag type mismatch (Structure Handle wrong) |
If STRING is part of a UDT:
// ✅ Recommended: Read entire UDT, modify STRING member, write back
var udt = client.ReadTag("gTestUDT");
// ... modify STRING member in UDT structure ...
client.WriteTag("gTestUDT", udt);If STRING is standalone:
// ❌ Direct write does NOT work
client.WriteString("gTest_STRING", "Hello"); // Fails with CIP Error 0x2107
// ❌ Writing .DATA and .LEN separately also does NOT work
client.WriteDint("gTest_STRING.LEN", 5); // Fails
client.WriteUdt("gTest_STRING.DATA", data); // Fails
// 💡 Alternative: Use PLC ladder logic intermediary
// 1. Create atomic buffer tag in PLC (e.g., StringBuffer : SINT[84])
// 2. Write to buffer from external device
// 3. Use ladder logic (COP instruction) to copy to STRING tag// ❌ Direct write to UDT array element member does NOT work
client.WriteDint("gTestUDT_Array[0].Member1_DINT", 42); // Fails with CIP Error 0x2107
// ✅ Recommended: Read-modify-write pattern
var element = client.ReadTag("gTestUDT_Array[0]");
// ... modify member in UDT structure ...
client.WriteTag("gTestUDT_Array[0]", element);// ✅ This works - writing entire UDT array element
var element = client.ReadTag("gTestUDT_Array[0]");
// ... modify UDT structure ...
client.WriteTag("gTestUDT_Array[0]", element);For complex scenarios:
- Create atomic buffer tags in the PLC
- Write to buffers from external device
- Use ladder logic (COP instruction) to copy buffers to structured tags
-
Rockwell 1756-PM020 - Logix 5000 Controllers Data Access https://literature.rockwellautomation.com/idc/groups/literature/documents/pm/1756-pm020_-en-p.pdf
-
Rockwell TypeEncode_CIPRW.pdf - Type Encoding of Logix Structures https://www.rockwellautomation.com/content/dam/rockwell-automation/sites/downloads/pdf/TypeEncode_CIPRW.pdf
-
Kepware ControlLogix Ethernet Driver Manual (Documents STRING handling limitations)
-
ODVA CIP Networks Library - Volume 1 (Requires license from ODVA)
The limitations you're experiencing are by design in the CIP protocol implementation for Logix controllers. Rockwell explicitly states that Predefined structures (including STRING) and complex structure operations should be handled by accessing individual atomic members rather than complete structures.
The workaround is to either:
- Access structure members individually (
.DATA,.LEN, etc.) - Use intermediary atomic tags with ladder logic
- Implement full CIP structure handling with proper Template reading and Structure Handle calculation
This is not a bug in your driver or code - it's a fundamental limitation of how CIP handles complex data types in Logix controllers.