PLC JSON Integration Plan¶
Overview¶
This document describes how to integrate the Essert.MF OPC UA Server with Siemens S7-1500 PLCs, specifically addressing JSON data exchange using the Siemens LStream library.
Reference Documentation:
- LStream Library: Siemens Entry ID 109781165
- Local copy: docs/OPCUA/109781165_LStream_DOC_v1_6_en.pdf
LStream Library Restrictions and Requirements¶
1. Encoding Restriction¶
| Requirement | Description |
|---|---|
| Encoding | ASCII only |
| Impact | Any other encoding (UTF-8 with special chars, Unicode) leads to undefined behavior |
| Risk | High - may cause issues during serialization/deserialization |
Server-Side Mitigation: - Ensure all string values contain only ASCII characters (0x00-0x7F) - Strip or replace non-ASCII characters before JSON serialization - Avoid German umlauts (ä, ö, ü, ß), special symbols, emojis
2. String Length Restriction¶
| Requirement | Description |
|---|---|
| Max Length | 256 characters per value |
| Applies To | Values of JSON elements, keys, attributes |
| Workaround | Modify library code (not recommended) |
Server-Side Mitigation: - Truncate all string values to max 250 characters (safety margin) - For long text fields, consider splitting or omitting from PLC responses - Create PLC-specific endpoints with guaranteed short values
Fields at Risk: | Field | Typical Length | Risk Level | Mitigation | |-------|----------------|------------|------------| | ProductName | < 100 | Low | Monitor | | DisplayName | < 100 | Low | Monitor | | ArticleNumber | < 50 | Low | None | | Description | Variable | High | Truncate or exclude | | NokReason | Variable | Medium | Truncate to 250 | | ErrorMessage | Variable | Medium | Truncate to 250 | | Creator | < 30 | Low | None |
3. JSON Format Restriction (CRITICAL)¶
| Requirement | Description |
|---|---|
| Format | Compressed JSON only |
| Line breaks | NOT allowed |
| Whitespace | NOT allowed outside of values |
Example - Invalid (Pretty-printed):
Example - Valid (Compressed):
Server Implementation Status:
- WriteIndented = false configured in both NodeManagers (via PlcJsonHelper)
- All JSON responses are compressed
- No line breaks or unnecessary whitespace
- All string values are truncated to 250 characters (safety margin)
- All non-ASCII characters are replaced with '?'
4. Data Type Handling¶
The LStream library stores all values as strings in LStream_typeElement.value. The PLC must convert these to appropriate data types.
Type Mapping:
| JSON Type | LStream type | PLC Conversion |
|---|---|---|
| Number (int) | 3 | STRING_TO_DINT() |
| Number (long) | 3 | STRING_TO_LINT() (if available) or split |
| Number (float) | 3 | STRING_TO_REAL() |
| String | 2 | Direct use |
| Boolean | 4 | value = 'true' check |
| Object | 0 | Container (no value) |
| Array | 1 | Container (no value) |
| Null | - | value = 'NULL' or value = 'null' |
OPC UA Server Configuration¶
Endpoint Configuration¶
{
"OpcUaServer": {
"EndpointUrl": "opc.tcp://192.168.100.110:48400/Essert.MF",
"ServerName": "Essert MF OPC UA Server",
"EnableSecurity": false
}
}
Available Namespaces¶
| Namespace | URI | Index |
|---|---|---|
| Products | http://essert.de/mf/products/ |
ns=2 |
| Manufacturing | http://essert.de/mf/manufacturing/ |
ns=3 |
OPC UA Methods and PLC Data Types¶
Products Namespace (ns=2)¶
| Method | Input Args | Output Type | PLC Return Type |
|---|---|---|---|
Ping |
- | String | String[254] |
GetProductCount |
- | Int32 | DInt |
GetProductsPaged |
skip: Int32, take: Int32 | String (JSON) | Array[*] of Byte |
GetAllProducts |
- | String (JSON) | Array[*] of Byte |
GetProductById |
productUid: Int64 | String (JSON) | Array[*] of Byte |
CreateProduct |
name, display, article: String | Int64 | LInt or DInt pair |
UpdateProduct |
uid: Int64, name: String | Boolean | Bool |
DeleteProduct |
uid: Int64 | Boolean | Bool |
AddVersion |
uid: Int64, name: String | Int64 | LInt or DInt pair |
DeleteVersion |
uid: Int64 | Boolean | Bool |
Manufacturing Namespace (ns=3)¶
| Method | Input Args | Output Type | PLC Return Type |
|---|---|---|---|
GetActiveProcessCount |
- | Int32 | DInt |
GetActiveProcesses |
skip, take: Int32 | String (JSON) | Array[*] of Byte |
GetProcessById |
uid: Int64 | String (JSON) | Array[*] of Byte |
GetProcessByOrderNumber |
order: String | String (JSON) | Array[*] of Byte |
GetProcessesByWpcId |
wpcUid: Int64 | String (JSON) | Array[*] of Byte |
CreateProcess |
(7 params) | Int64 | LInt or DInt pair |
StartProcess |
uid: Int64 | Boolean | Bool |
CompleteProcess |
uid: Int64 | Boolean | Bool |
FailProcess |
uid: Int64, reason: String | Boolean | Bool |
CancelProcess |
uid: Int64, reason: String | Boolean | Bool |
DeleteProcess |
uid: Int64 | Boolean | Bool |
JSON Response Structures¶
GetProductsPaged Response¶
{"Items":[{"Uid":1,"ProductName":"Product A","DisplayName":"Display A","ArticleNumber":"ART-001","Versions":[]}],"TotalCount":42,"Skip":0,"Take":10,"TotalPages":5,"CurrentPage":1,"HasNextPage":true,"HasPreviousPage":false}
LStream Tree Structure: | Index | type | key | value | depth | closingElement | |-------|------|-----|-------|-------|----------------| | 0 | 0 | (root) | | 1 | false | | 1 | 1 | Items | | 2 | false | | 2 | 0 | | | 3 | false | | 3 | 3 | Uid | 1 | 4 | false | | 4 | 2 | ProductName | Product A | 4 | false | | 5 | 2 | DisplayName | Display A | 4 | false | | 6 | 2 | ArticleNumber | ART-001 | 4 | true | | 7 | 3 | TotalCount | 42 | 2 | false | | ... | ... | ... | ... | ... | ... |
GetProductById Response¶
{"Uid":1,"ProductName":"Product A","DisplayName":"Display A","ArticleNumber":"ART-001","Versions":[{"Uid":10,"VersionName":"V1.0"}]}
PLC Implementation Guidelines¶
1. Data Block Setup¶
DB_OpcUa_Products
├── execute : Bool
├── connectionId : DWord
├── inputArgs
│ ├── skip : DInt
│ └── take : DInt
├── outputRaw : Array[0..9999] of Byte
├── outputTree : Array[0..99] of LStream_typeElement
├── status
│ ├── busy : Bool
│ ├── done : Bool
│ ├── error : Bool
│ └── errorCode : Word
└── result
├── totalCount : DInt
├── currentPage : DInt
└── products : Array[0..9] of UDT_Product
2. Workflow Sequence¶
1. [OPC_UA_Connect] → Establish connection
2. [OPC_UA_MethodCall] → Call GetProductsPaged(skip, take)
3. [String to Bytes] → Convert response to byte array
4. [LStream_JsonDeserializer] → Parse JSON to tree
5. [Map to UDT] → Extract values to PLC structure
6. [Use data] → Application logic
3. Error Handling¶
| LStream Status | Meaning | Action |
|---|---|---|
| 16#0000 | Success | Process data |
| 16#8201 | Array too small | Increase tree/byte array size |
| 16#8401 | No raw data | Check OPC UA response |
| 16#8600 | State machine error | Reset and retry |
Implementation Checklist¶
Server Side (Essert.MF.API.OpcUa)¶
- JSON serialization uses compressed format (
WriteIndented = false) - OPC UA methods have proper
OpcArgumentAttributeannotations - Add string truncation helper for values > 250 chars (
PlcJsonHelper.SanitizeString) - Add ASCII validation/sanitization for string values (
PlcJsonHelper.Serialize) - Add unit tests for PlcJsonHelper (35 tests)
- Add integration tests for max string length scenarios (11 tests)
- [~] ~~Create PLC-optimized endpoints~~ — Not needed: The existing endpoints with
PlcJsonHelperalready handle all LStream constraints. PLCs can use pagination (e.g.,GetProductsPaged(0, 10)) to control payload size and ignore unused fields. Revisit only if PLC integration testing reveals specific memory or parsing issues.
PLC Side (TIA Portal)¶
Note: Most of these items can be auto-generated using the PLC Code Generator.
- Import LStream library (V1.6+)
- Configure OPC UA client connection
- Create UDTs for product/process data (can be generated)
- Create data blocks for method parameters (can be generated)
- Implement OPC UA method call sequence (can be generated)
- Implement JSON deserialization with LStream (can be generated)
- Implement value extraction and type conversion (can be generated)
- Add error handling for all failure scenarios
Testing Recommendations¶
1. Connectivity Test¶
- Use
Pingmethod (no parameters, returns "pong") - Verify OPC UA connection and method call works
2. Simple Data Test¶
- Use
GetProductCount(returns single DInt) - No JSON parsing required
3. JSON Parsing Test¶
- Use
GetProductsPaged(0, 1)(single product) - Verify LStream deserializes correctly
- Check all fields are accessible
4. Boundary Tests¶
- Test with maximum string lengths (250 chars)
- Test with special ASCII characters
- Test with empty arrays/null values
References¶
- PLC Code Generator Plan - Automatic generation of PLC code from OPC UA metadata
- LStream Library Documentation
- OPC UA Client for S7-1500
- TIA Portal OPC UA Configuration
Document History¶
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-21 | Claude | Initial version |
| 1.1 | 2026-01-21 | Claude | Implemented PlcJsonHelper with ASCII sanitization and string truncation |
| 1.2 | 2026-01-21 | Claude | Marked PLC-optimized endpoints as not needed (existing endpoints sufficient) |