Skip to content

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

  1. Connection Panel: Configure OPC UA server connection within TIA Portal
  2. Method Browser: Tree view of available OPC UA namespaces and methods
  3. Selection Dialog: Choose which methods to generate code for
  4. Preview: Show generated code before committing
  5. Sync Status: Indicate which blocks are out of sync with server
  6. 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 browse command
  • Implement generate command
  • Implement validate command
  • 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 server
  • System.Text.Json - JSON parsing for schema inference

CLI Tool

  • System.CommandLine - Command-line parsing
  • Microsoft.Extensions.Configuration - Configuration handling

TIA Portal Add-in (Future)

  • Siemens.Engineering - TIA Openness API
  • Siemens.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