PLC Code Generator Plan¶
Overview¶
This document describes the plan for automatically generating Siemens S7-1500 PLC code (UDTs, Data Blocks, Function Blocks) from the OPC UA server method descriptions. The goal is to eliminate manual coding and ensure consistency between the OPC UA server and PLC implementation.
Related Documentation: - PLC JSON Integration Plan - LStream constraints and JSON handling - LStream Library Documentation
Goals¶
End Goal¶
A TIA Portal Add-in that: 1. Connects to the OPC UA server from within TIA Portal 2. Browses the server's method descriptions and type information 3. Generates PLC blocks and data structures directly using the TIA Openness API 4. Keeps PLC code in sync with server changes
Intermediate Goal¶
A CLI tool that: 1. Connects to the OPC UA server 2. Extracts method metadata and infers JSON schemas 3. Generates SCL source files that can be manually imported into TIA Portal 4. Uses the same core library that will power the TIA Portal Add-in
Architecture¶
Layered Design for Reuse¶
The architecture is designed so the core logic can be reused across different frontends:
┌─────────────────────────────────────────────────────────────┐
│ TIA Portal Add-in (Future) │
│ Uses Openness API for direct block creation │
│ │
│ - Integrated into TIA Portal UI │
│ - Direct block creation (no file import needed) │
│ - Real-time sync with OPC UA server │
└─────────────────────────────────────────────────────────────┘
│
│ uses
▼
┌─────────────────────────────────────────────────────────────┐
│ CLI Tool (Intermediate) │
│ Generates SCL files for manual import │
│ │
│ - Command-line interface │
│ - Outputs .scl files │
│ - Manual import into TIA Portal │
└─────────────────────────────────────────────────────────────┘
│
│ uses
▼
┌─────────────────────────────────────────────────────────────┐
│ Core Library │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │
│ │ OPC UA Metadata │ │ Type Mapping │ │ Code Gen │ │
│ │ Reader │ │ JSON → PLC │ │ Interface │ │
│ │ │ │ │ │ │ │
│ │ - Browse nodes │ │ - Type mapping │ │ - UDTs │ │
│ │ - Extract args │ │ - Array sizing │ │ - DBs │ │
│ │ - Infer schema │ │ - String limits │ │ - FBs │ │
│ └─────────────────┘ └─────────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
Project Structure¶
Essert.MF.Tools.PlcCodeGenerator/
│
├── Essert.MF.PlcCodeGen.Core/ # Reusable core library
│ │
│ ├── OpcUa/ # OPC UA metadata extraction
│ │ ├── IOpcUaMetadataReader.cs # Interface for testability
│ │ ├── OpcUaMetadataReader.cs # Implementation using Opc.UaFx
│ │ └── Models/
│ │ ├── OpcUaMethodInfo.cs # Method name, description, node ID
│ │ ├── OpcUaArgumentInfo.cs # Argument name, type, description
│ │ └── OpcUaNamespaceInfo.cs # Namespace URI, available methods
│ │
│ ├── Schema/ # JSON schema inference
│ │ ├── ISchemaInferrer.cs # Interface
│ │ ├── JsonSchemaInferrer.cs # Infers schema from sample JSON
│ │ └── Models/
│ │ ├── JsonSchema.cs # Root schema
│ │ ├── JsonProperty.cs # Property definition
│ │ └── JsonArrayInfo.cs # Array element type, typical size
│ │
│ ├── TypeMapping/ # JSON to PLC type mapping
│ │ ├── ITypeMapper.cs # Interface
│ │ ├── JsonToPlcTypeMapper.cs # Maps JSON types to S7 types
│ │ ├── PlcType.cs # Enum: Bool, Int, DInt, LInt, Real, String, UDT, Array
│ │ └── PlcTypeDefinition.cs # Full type info with constraints
│ │
│ ├── CodeGen/ # Code generation
│ │ ├── IPlcCodeGenerator.cs # Abstract interface
│ │ ├── SclCodeGenerator.cs # Generates .scl text files
│ │ ├── OpennessCodeGenerator.cs # Future: TIA Openness API
│ │ ├── Models/
│ │ │ ├── GeneratedUdt.cs # UDT definition
│ │ │ ├── GeneratedDataBlock.cs # DB definition
│ │ │ ├── GeneratedFunctionBlock.cs # FB definition
│ │ │ └── GenerationResult.cs # Collection of all generated items
│ │ └── Templates/
│ │ ├── SclUdtTemplate.cs # UDT SCL template
│ │ ├── SclDataBlockTemplate.cs # DB SCL template
│ │ └── SclFunctionBlockTemplate.cs # FB SCL template with LStream
│ │
│ └── Configuration/
│ ├── GeneratorOptions.cs # Configuration options
│ └── TypeMappingOptions.cs # Custom type mappings
│
├── Essert.MF.PlcCodeGen.Cli/ # CLI tool (intermediate solution)
│ ├── Program.cs # Entry point
│ ├── Commands/
│ │ ├── GenerateCommand.cs # Main generation command
│ │ ├── BrowseCommand.cs # Browse server and list methods
│ │ └── ValidateCommand.cs # Validate generated code
│ └── appsettings.json # Default configuration
│
├── Essert.MF.PlcCodeGen.TiaAddin/ # Future: TIA Portal Add-in
│ ├── AddInProvider.cs # TIA Portal add-in entry point
│ ├── UI/
│ │ ├── ConnectionDialog.xaml # OPC UA connection settings
│ │ ├── MethodSelectionDialog.xaml # Select methods to generate
│ │ └── GenerationOptionsDialog.xaml # Configure generation
│ └── Openness/
│ └── TiaOpennessCodeGenerator.cs # Implements IPlcCodeGenerator
│
└── Essert.MF.PlcCodeGen.Tests/ # Unit and integration tests
├── OpcUa/
│ └── OpcUaMetadataReaderTests.cs
├── Schema/
│ └── JsonSchemaInferrerTests.cs
├── TypeMapping/
│ └── JsonToPlcTypeMapperTests.cs
└── CodeGen/
└── SclCodeGeneratorTests.cs
Type Mapping¶
JSON to S7 Type Mapping¶
| JSON Type | JSON Example | LStream Type | S7 Data Type | Notes |
|---|---|---|---|---|
| Integer (small) | 123 |
3 | DInt |
-2,147,483,648 to 2,147,483,647 |
| Integer (large) | 9999999999 |
3 | LInt |
For UIDs and large numbers |
| Float | 3.14159 |
3 | Real |
32-bit floating point |
| String | "Hello" |
2 | String[250] |
LStream max 256, use 250 for safety |
| Boolean | true |
4 | Bool |
LStream stores as "true"/"false" |
| Object | {...} |
0 | UDT_* |
Generate UDT for each object type |
| Array | [...] |
1 | Array[0..n] of * |
Size from config or sample data |
| Null | null |
- | (optional field) | Handle as empty/default value |
String Length Handling¶
All string fields are limited to 250 characters (LStream constraint). The generator will:
1. Use String[250] as the default type
2. Allow configuration overrides for known shorter fields
3. Generate comments noting the truncation behavior
Array Sizing Strategy¶
Arrays require a fixed size in S7. The generator will: 1. Use a configurable default size (e.g., 10 items) 2. Infer from sample data if available 3. Allow per-field overrides in configuration 4. Generate constants for easy adjustment
Generated Output¶
Example: Products Namespace¶
Given the OPC UA server's Products namespace, the generator would produce:
UDT_ProductVersion.scl¶
TYPE "UDT_ProductVersion"
VERSION : 0.1
STRUCT
Uid : LInt; // Version unique identifier
VersionName : String[250]; // Version name (e.g., "V1.0")
END_STRUCT;
END_TYPE
UDT_Product.scl¶
TYPE "UDT_Product"
VERSION : 0.1
STRUCT
Uid : LInt; // Product unique identifier
ProductName : String[250]; // Internal product name
DisplayName : String[250]; // Display name for UI
ArticleNumber : String[250]; // Article/part number
Versions : Array[0..9] of "UDT_ProductVersion"; // Product versions
VersionCount : Int; // Actual number of versions
END_STRUCT;
END_TYPE
UDT_ProductsPagedResult.scl¶
TYPE "UDT_ProductsPagedResult"
VERSION : 0.1
STRUCT
Items : Array[0..9] of "UDT_Product"; // Product items
ItemCount : Int; // Actual number of items
TotalCount : DInt; // Total products in database
Skip : DInt; // Number of skipped items
Take : DInt; // Number of requested items
TotalPages : DInt; // Total number of pages
CurrentPage : DInt; // Current page number
HasNextPage : Bool; // More pages available
HasPreviousPage : Bool; // Previous pages available
END_STRUCT;
END_TYPE
FB_Products_GetProductsPaged.scl¶
FUNCTION_BLOCK "FB_Products_GetProductsPaged"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
VAR_INPUT
Execute : Bool; // Rising edge triggers execution
Skip : DInt := 0; // Pagination: items to skip
Take : DInt := 10; // Pagination: items to return
END_VAR
VAR_OUTPUT
Done : Bool; // Execution completed
Busy : Bool; // Execution in progress
Error : Bool; // Error occurred
ErrorCode : Word; // Error code (0 = no error)
Result : "UDT_ProductsPagedResult"; // Parsed result
END_VAR
VAR
// OPC UA call state machine
_state : Int;
_opcUaClient : "OPC_UA_Client"; // Reference to OPC UA client FB
// LStream parsing
_jsonRaw : Array[0..9999] of Byte;
_jsonTree : Array[0..199] of "LStream_typeElement";
_lstreamStatus : Word;
END_VAR
VAR_TEMP
_edgeDetect : Bool;
END_VAR
BEGIN
// State machine for OPC UA method call and JSON parsing
// ... (generated LStream parsing logic)
END_FUNCTION_BLOCK
CLI Tool Usage¶
Commands¶
# Browse server and list available methods
plccodegen browse --server "opc.tcp://192.168.100.110:48400/Essert.MF"
# Generate code for all namespaces
plccodegen generate \
--server "opc.tcp://192.168.100.110:48400/Essert.MF" \
--output "./Generated/"
# Generate code for specific namespace
plccodegen generate \
--server "opc.tcp://192.168.100.110:48400/Essert.MF" \
--namespace "http://essert.de/mf/products/" \
--output "./Generated/Products/"
# Generate with custom configuration
plccodegen generate \
--server "opc.tcp://192.168.100.110:48400/Essert.MF" \
--config "./plccodegen.json" \
--output "./Generated/"
# Validate existing generated code against server
plccodegen validate \
--server "opc.tcp://192.168.100.110:48400/Essert.MF" \
--source "./Generated/"
Configuration File (plccodegen.json)¶
{
"server": {
"endpointUrl": "opc.tcp://192.168.100.110:48400/Essert.MF",
"securityMode": "None",
"timeout": 30000
},
"generation": {
"outputDirectory": "./Generated",
"namespaces": [
"http://essert.de/mf/products/",
"http://essert.de/mf/manufacturing/"
],
"generateUdts": true,
"generateDataBlocks": true,
"generateFunctionBlocks": true,
"includeComments": true
},
"typeMapping": {
"defaultArraySize": 10,
"defaultStringLength": 250,
"overrides": {
"ArticleNumber": { "stringLength": 50 },
"Versions": { "arraySize": 20 }
}
},
"naming": {
"udtPrefix": "UDT_",
"dbPrefix": "DB_",
"fbPrefix": "FB_",
"usePascalCase": true
}
}
Output Structure¶
Generated/
├── Products/
│ ├── UDTs/
│ │ ├── UDT_Product.scl
│ │ ├── UDT_ProductVersion.scl
│ │ └── UDT_ProductsPagedResult.scl
│ ├── DataBlocks/
│ │ └── DB_Products.scl
│ ├── FunctionBlocks/
│ │ ├── FB_Products_Ping.scl
│ │ ├── FB_Products_GetProductCount.scl
│ │ ├── FB_Products_GetProductsPaged.scl
│ │ ├── FB_Products_GetProductById.scl
│ │ ├── FB_Products_CreateProduct.scl
│ │ ├── FB_Products_UpdateProduct.scl
│ │ ├── FB_Products_DeleteProduct.scl
│ │ ├── FB_Products_AddVersion.scl
│ │ └── FB_Products_DeleteVersion.scl
│ └── _ImportOrder.txt
│
└── Manufacturing/
├── UDTs/
│ ├── UDT_ManufacturingProcess.scl
│ └── UDT_ProcessState.scl
├── DataBlocks/
│ └── DB_Manufacturing.scl
└── FunctionBlocks/
├── FB_Manufacturing_GetActiveProcesses.scl
├── FB_Manufacturing_GetProcessById.scl
├── FB_Manufacturing_CreateProcess.scl
└── ...
TIA Portal Add-in (Future)¶
Integration with TIA Openness API¶
The TIA Portal Add-in will use the same core library but replace file generation with direct block creation:
// Instead of generating SCL files:
var sclGenerator = new SclCodeGenerator();
sclGenerator.Generate(metadata, outputPath);
// The add-in will create blocks directly:
var opennessGenerator = new TiaOpennessCodeGenerator(tiaPortalProject);
opennessGenerator.Generate(metadata); // Creates blocks in TIA Portal
Add-in Features¶
- Connection Panel: Configure OPC UA server connection within TIA Portal
- Method Browser: Tree view of available OPC UA namespaces and methods
- Selection Dialog: Choose which methods to generate code for
- Preview: Show generated code before committing
- Sync Status: Indicate which blocks are out of sync with server
- Regeneration: Update existing blocks when server changes
Openness API Usage¶
// Example: Creating a UDT using TIA Openness
public void CreateUdt(PlcSoftware plc, GeneratedUdt udt)
{
var udtGroup = plc.TypeGroup.Groups.Find("Generated_UDTs")
?? plc.TypeGroup.Groups.Create("Generated_UDTs");
var plcType = udtGroup.Types.Create(udt.Name);
foreach (var member in udt.Members)
{
plcType.Members.Create(member.Name, member.DataType);
}
}
// Example: Creating a Function Block
public void CreateFunctionBlock(PlcSoftware plc, GeneratedFunctionBlock fb)
{
var fbGroup = plc.BlockGroup.Groups.Find("Generated_FBs")
?? plc.BlockGroup.Groups.Create("Generated_FBs");
var block = fbGroup.Blocks.CreateFB(fb.Name, fb.Number);
// Add interface (inputs, outputs, statics)
// Import SCL code for implementation
}
Implementation Phases¶
Phase 1: Core Library (Intermediate)¶
- Create project structure
- Implement
OpcUaMetadataReader- browse server, extract method info - Implement
JsonSchemaInferrer- infer schema from sample responses - Implement
JsonToPlcTypeMapper- map JSON types to S7 types - Implement
SclCodeGenerator- generate SCL text files - Add unit tests for all components
Phase 2: CLI Tool (Intermediate)¶
- Create CLI project with System.CommandLine
- Implement
browsecommand - Implement
generatecommand - Implement
validatecommand - Add configuration file support
- Create documentation and examples
Phase 3: TIA Portal Add-in (Future)¶
- Set up TIA Openness development environment
- Create add-in project structure
- Implement connection dialog UI
- Implement method browser UI
- Implement
TiaOpennessCodeGenerator - Add sync/update functionality
- Testing with TIA Portal V17/V18/V19
Dependencies¶
Core Library¶
Opc.UaFx.Client- OPC UA client for browsing serverSystem.Text.Json- JSON parsing for schema inference
CLI Tool¶
System.CommandLine- Command-line parsingMicrosoft.Extensions.Configuration- Configuration handling
TIA Portal Add-in (Future)¶
Siemens.Engineering- TIA Openness APISiemens.Engineering.Hmi- HMI integration (optional)
Risks and Mitigations¶
| Risk | Impact | Mitigation |
|---|---|---|
| JSON schema inference inaccurate | Wrong PLC types generated | Allow manual overrides in config; validate against sample data |
| Array sizes too small/large | Runtime errors or wasted memory | Configurable defaults; analyze sample data; generate constants |
| LStream parsing complexity | Generated FB code hard to debug | Generate clear comments; provide debug variables |
| TIA Openness API limitations | Cannot create all block types | Fall back to SCL import for complex cases |
| OPC UA server unavailable | Cannot generate code | Cache last known metadata; offline mode |
Document History¶
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | 2026-01-21 | Claude | Initial version |