Files
lmxopcua/docs/drivers/S7-TIA-Import.md
2026-04-26 06:50:26 -04:00

11 KiB

S7 — TIA Portal CSV & STEP 7 Classic AWL symbol import

PR-S7-D1 / #299 — bulk-import TIA Portal "Show all tags" CSV exports and STEP 7 Classic AWL declaration files into the S7 driver. Saves operators from hand-typing every %MW0 / %DB1.DBW0 row of a several-hundred-tag PLC into appsettings.json.

Supported formats — v1

Format Status Notes
TIA Portal .CSV ("Show all tags" export) supported Header columns Name,Path,Data type,Logical address,Comment,Hmi accessible,…; en-US (,) and DE-locale (; separator + , decimal) auto-detected
STEP 7 Classic .AWL (VAR_GLOBAL + DATA_BLOCK) supported, best-effort Position-based offset assignment (no exact byte offsets in hand-exported AWL — see below)
STEP 7 / TIA Portal native binary (.s7p, .zap) out of scope Proprietary; no community parser. Use TIA's "Show all tags" CSV export
TIA Portal Openness API out of scope Requires a licensed TIA install + OpenAPI license; future PR

TIA Portal CSV column reference

Column Required Notes
Name yes OPC UA tag name. TIA symbols are stable across deployments; the importer uses them verbatim
Logical address (or Address) yes TIA-style address with leading % (e.g. %MW0, %DB1.DBW10, %DB1.DBX2.3). Stripped on import
Data type recommended TIA primitive type (Int, Real, Bool, String, …) — drives the imported S7DataType
Comment no Parsed but currently unused — S7TagDefinition has no Description field at the v2 schema layer (see #248). Held in the column contract for future schema bumps
Hmi accessible no Filter — rows with False / FALSCH / nein are skipped (internal symbols TIA shows in the editor but doesn't expose to client interfaces). Missing column defaults to True
Hmi visible / Hmi writeable no Currently unused — held for future Admin-UI-side metadata
Length no For String rows: max length. Default 254. Drives StringLength on the imported tag
Path no TIA tag-table path (Default tag table, custom names). Currently unused; held in the contract

TIA Data typeS7DataType mapping

TIA type Maps to Notes
Bool Bool Bit access; address must include a .bit suffix
Byte, SInt, USInt Byte 1-byte unsigned/signed
Int Int16 Signed 16-bit
Word, UInt UInt16 Unsigned 16-bit
DInt Int32 Signed 32-bit
DWord, UDInt UInt32 Unsigned 32-bit
LInt Int64 64-bit signed (S7-1500 only)
LWord, ULInt UInt64 64-bit unsigned (S7-1500 only)
Real Float32 IEEE-754 32-bit
LReal Float64 IEEE-754 64-bit (S7-1500 only)
String String S7 STRING with 2-byte header; Length column drives StringLength
WString WString S7 WSTRING (UTF-16BE)
Char / WChar Char / WChar Single-character
Date Date UInt16 days since 1990-01-01
Time Time Int32 ms
TOD / Time_Of_Day TimeOfDay UInt32 ms since midnight
DT / Date_And_Time DateAndTime 8-byte BCD
DTL Dtl 12-byte structured (S7-1200 / S7-1500)
S5Time S5Time 16-bit BCD duration
Struct / quoted UDT name UDT placeholder See below

UDT placeholders

UDT-typed symbols (TIA Data type = "MyUdt" quoted, or the literal Struct) import as a placeholder — the resulting tag lands in the driver options so it shows up in the Admin UI tag list, but its data type is forced to Byte and the row is marked Writable = false.

S7ImportResult.UdtPlaceholderCount tracks how many of the imported tags landed in this bucket.

Cooperation with Udts declarations (PR-S7-D2 / #300)

PR-S7-D2 ships UDT fan-out via S7DriverOptions.Udts + S7TagDefinition.UdtName. The importer and the Udts declaration cooperate as follows:

  1. The importer emits a placeholder row for each UDT-typed symbol — same as today (data type forced to Byte, Writable = false).
  2. The operator hand-edits the placeholder row in the resulting JSON / options object and:
    • Sets UdtName to the UDT type name from the TIA "Data type" column
    • Removes the Writable: false marker (UDT leaves inherit the parent's writability)
  3. The operator declares the matching S7UdtDefinition in S7DriverOptions.Udts (member offsets come from the TIA UDT definition in the project file — TIA's "Show all tags" CSV does not export struct field offsets, hence the manual layout step).
  4. At driver init, the fan-out replaces the placeholder with one scalar leaf per UDT member.

The importer does NOT auto-populate Udts — UDT layouts live in the project file, not the symbol-table CSV. A future enhancement may parse the SCL UDT declaration alongside the CSV; for now the cooperation is "importer flags it, operator declares the layout, driver fans out at init".

See docs/v2/s7.md "UDT / STRUCT support" for the full fan-out semantics, the 4-level nesting cap, and the Optimized-block-access prerequisite.

DE locale handling

TIA Portal honours the Windows display locale when writing CSV. A DE-locale install emits:

  • Field separator ; (because , is the decimal separator)
  • Decimal-comma in addresses: %MW0,5 rather than %MW0.5 for bit addresses
  • Boolean column values WAHR / FALSCH rather than True / False

The importer auto-detects the locale from the first non-blank line:

  • Field-separator detection: counts ; vs , occurrences in the header
  • Decimal-comma detection: scans the first data row's address column for a digit-comma-digit pattern
  • Boolean column values: recognises both languages (true/false/wahr/falsch/yes/no/ja/nein, case-insensitive) plus bare 0/1

The address column is rewritten to en-US shape (%MW0,5MW0.5) before the strict S7AddressParser runs, so the rest of the driver pipeline sees a single canonical address shape.

STEP 7 Classic AWL — VAR_GLOBAL + DATA_BLOCK

Best-effort parser for legacy STEP 7 Classic projects:

  • VAR_GLOBAL … END_VAR — global memory area declarations. Each entry maps to a sequential M{B|W|D}{offset} address based on declaration order.
  • DATA_BLOCK DBn … END_DATA_BLOCK — DB declarations. Each field maps to a DB{n}.DB{B|W|D}{offset} address based on declaration order; the DB number is parsed from the DATA_BLOCK line's DBn keyword.

Position-based addressing — heuristic

Real STEP 7 Classic projects carry exact byte offsets in the symbol table / .gr8 deployment artefact, but a hand-exported AWL file omits them. The importer assumes:

Type Bytes
BOOL 1 (rounded up to byte alignment)
BYTE / SINT / USINT / CHAR 1
INT / WORD / UINT 2
DINT / DWORD / UDINT / REAL 4
LREAL / LINT / ULINT / LWORD 8
STRING[N] N + 2 (2-byte header)
STRING (no length) 256
STRUCT / Array[…] of … / quoted UDT name UDT placeholder (8-bit Byte at next aligned offset)

S7 alignment rule: offsets round up to a 2-byte boundary for any 16-bit-or-larger type. Sites needing exact offsets should drive their symbol import from the TIA Portal CSV path instead — the CSV carries the offsets verbatim.

Comments ((* ... *) block, // ... line) are stripped before declaration parsing. Initial-value clauses (:= 0) are recognised and discarded.

CLI subcommand — import-symbols

otopcua-s7-cli import-symbols --help
Flag Default Purpose
-f / --file required Path to the TIA CSV or .AWL file
--format tia tia (CSV) or awl (STEP 7 Classic)
-d / --device none Optional documentation tag (reserved for symmetry with import-rslogix)
--emit appsettings-fragment appsettings-fragment (JSON) or summary (one-line counter)
-o / --output stdout Optional path; when set the JSON fragment is written there + summary line goes to stdout
--max-rows unlimited Defensive cap on rows imported
--strict off Fail-fast on the first malformed row (default permissive: skip + log)

appsettings-fragment output shape

The default --emit appsettings-fragment mode writes a JSON object whose Tags array is shaped like the S7DriverConfigDto.Tags array — paste straight into the driver-instance config under Drivers/<instance>/Config/Tags.

{
  "Tags": [
    {
      "Name": "MotorSpeed",
      "Address": "MW0",
      "DataType": "Int16",
      "Writable": true,
      "StringLength": 254
    },
    
  ]
}

Summary line

--emit summary writes a single line:

Imported 142 tag(s), skipped 3, errors 0, udt-placeholders 5.

Skipped covers HMI-accessible-false rows + missing-required-field rows; errors covers rows whose Address failed to parse as an S7 address; udt-placeholders covers UDT-typed rows that imported as placeholders.

API surface — IS7SymbolImporter + AddTiaCsvImport / AddAwlImport

For server-side / bootstrap use-cases the importer is reachable via:

using ZB.MOM.WW.OtOpcUa.Driver.S7;
using ZB.MOM.WW.OtOpcUa.Driver.S7.SymbolImport;

var options = new S7DriverOptions { Host = "192.168.1.30", CpuType = CpuType.S71500 };

// Append imported tags onto an existing options object.
var updated = options.AddTiaCsvImport(
    path: @"C:\plc\tia-export.csv",
    out var result);

Console.WriteLine($"Imported {result.ParsedCount} tags ({result.UdtPlaceholderCount} placeholders)");

// AWL variant — same shape.
var withAwl = updated.AddAwlImport(
    path: @"C:\plc\classic.awl",
    out var awlResult);

For a hand-managed importer instance (e.g. supplying a custom ILogger) call new TiaCsvImporter(logger).Parse(stream, opts) or new AwlImporter(logger).Parse(stream, opts) directly.

Operational notes

  • The importers are additiveAddTiaCsvImport / AddAwlImport concatenate onto the existing Tags list rather than replacing it. Hand-rolled tags (system-status variables, computed fields the operator added by hand) survive a re-import.
  • Re-imports are not idempotent — calling AddTiaCsvImport twice will produce duplicate tag rows. Operators are expected to start from a clean options object or de-duplicate themselves; a future schema rev may add a replace=true switch.
  • UDT placeholders surface in the Admin UI as non-writable Byte tags. PR-S7-D2 added the runtime UDT fan-out (S7DriverOptions.Udts + S7TagDefinition.UdtName) — operators upgrade a placeholder row by setting UdtName and declaring the matching S7UdtDefinition; see "Cooperation with Udts declarations" above. Placeholder-only rows still work as a Byte view of the first byte but can't browse / read their members until the layout is declared.
  • Description metadata is dropped on the floor today — see the column reference above. When #248 lands a Description field on S7TagDefinition the importer will start populating it without further changes to the CSV contract.