WinCC V7 Tag Archiving: Cyclic Action vs Event Triggers

David Krause13 min read
Best PracticesSCADA ConfigurationSiemens
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

1. Overview: The Multi-Tag Archiving Problem

WinCC V7 projects frequently need to persist process state into a User Archive whenever one of many text tags changes value. The original question came from an integrator tracking 40 text tags, but the same pattern applies to 8 tags, 80 tags, or several hundred. The naïve design — one event-triggered action per tag — works, but it inflates the action tree, multiplies the load on the WinCC scheduling engine, and makes future tag-list changes a maintenance burden.

This reference documents the recommended pattern: a single cyclic action that uses GetTagMultiWait (C scripting) or the HMIRuntime.Tags TagSet (VBS) to bulk-read 40 process tags, compares each value to a stored "last value" in a paired set of internal tags, and writes only the rows that actually changed. The pattern collapses the action count from N to 1, keeps the trigger logic in one auditable location, and isolates the User Archive write path so it can be rate-limited and monitored.

Scope. The recommendations apply to WinCC V7.4 SP9 and WinCC V7.5 SP2 (and later V7.5 updates). They are also valid for WinCC Professional (TIA Portal) when the project uses PC-RT runtime, with syntax adjustments noted in the implementation section.

2. Architectural Options Compared

Four common approaches exist for archiving many tag changes. The table below evaluates each against the design constraints typical of a 40-tag, 1-Hz process.

Approach Trigger Source Action Count Reliability Maintainability CPU Overhead
40 actions, one per tag Tag-change event 40 High (one trigger = one write) Low (40 places to maintain) High (40 scheduler entries, 40 stack frames)
1 action, 40 tag triggers Tag-change event (multi) 1 Medium (action fires once per change but cannot easily distinguish which tag changed) Medium (OR-logic in trigger list) Medium-High (action re-enters on every change; no batching)
1 cyclic action, manual compare in body 1-min cyclic timer 1 High (deterministic scan, no missed events) High (single source of truth) Low (bulk read, batched compare, batched write)
1 cyclic action reading directly from PLC (no internal "last value") 1-min cyclic 1 Medium (cannot detect intermediate changes within the cycle window) High Lowest

For text tags that may change between cycles, the cyclic + paired-internal-tag pattern is the most reliable. It guarantees every value transition is captured while keeping the action count, the scheduler load, and the code surface area at a minimum.

3. WinCC Action and Trigger Limits

Before committing to any of the architectures above, validate that the project stays inside WinCC's documented limits. Exceeding these thresholds results in runtime warnings, dropped actions, or, in extreme cases, the WinCC scheduler refusing to load additional actions.

Parameter Default / Limit (WinCC V7.5) Effect at Limit
Actions per project Configurable in Computer Properties → Runtime → "Maximum number of actions" (default 5000) Scheduler logs an entry; new actions are not loaded
Tag triggers per action Up to 250 tag-triggers per action (combined cycle + event) Long trigger list slows scheduler evaluation
Action cycle minimum 250 ms (Cyclic trigger, 1 s default) Faster cycles are ignored
VBS script length ~32,000 characters per action Compile error at save time
User Archive rows per request Configurable (default 1,000 visible; SQL-side limit applies) Time-out; partial commit

For a 40-tag project the limits are not at risk, but the same code is often reused in 500-tag plants. Keep the action footprint flat: one cyclic action, one C/VBS file, and parameterize the tag list from a User Archive configuration table rather than hard-coding.

General event-limit context. Outside WinCC, HTTP/2 caps simultaneous event streams at a negotiated default of 100 per MDN: EventSource. The same "fewer triggers, more batching" principle applies to OS event logs — Microsoft: Event Viewer log sizing documents how unbounded event sources saturate the consumer.

4. Recommended Architecture: Cyclic Comparison Pattern

The recommended pattern consists of four cooperating elements:

  1. N process tags (the 40 text tags) — external, OPC-sourced or internal from the PLC.
  2. N internal mirror tags — WinCC internal tags that hold the last archived value of each process tag.
  3. One cyclic action triggered every minute (or 30 s for faster plants). It reads all N process tags in one call, reads all N mirror tags in one call, compares the two arrays, and writes only the changed rows to the User Archive. After a successful write it updates the mirror tags.
  4. One User Archive table with columns: TagName (string), TagValue (string), Timestamp (date/time), Quality (smallint, optional).

This keeps the action deterministic: the time it takes is bounded by the User Archive write, not by the number of changed tags, because the read and the compare are bulk operations.

5. User Archive Table Configuration

Create the archive in WinCC Explorer under User Archive → New Archive → New Table. Use the column definitions below.

Column Data Type Length / Precision Allow NULL Notes
ID Auto-increment integer — No Primary key, set by UA engine
TagName Text 64 No Matches the WinCC tag name exactly
TagValue Text 255 Yes String-casted from the source tag
Timestamp Date/Time — No Default = GetDate() via UA
Quality Smallint — Yes Maps to OPC quality codes (192 = Good)

Enable Logging on the archive so changes are visible in CCUAHole views. For retention, configure an archive segment size and a backup path; the default segment is 30 days, which is usually adequate for text-tag audit trails.

6. VBS Action Implementation

The action is added in WinCC Explorer under Global Actions → New Action. Set the trigger to Cyclic, 1 minute. The body uses the HMIRuntime.Tags TagSet object for bulk reads and the HMIRuntime.DataSet for User Archive writes.

<%
' --- WinCC V7 cyclic archive action (1 min) ---
Option Explicit

Const TAG_COUNT = 40
Const CYCLE_S = 60

' Tag name arrays. In production, load from a configuration User Archive.
Dim procNames(TAG_COUNT-1)
Dim intNames(TAG_COUNT-1)
procNames(0)  = "Line1_State":      intNames(0)  = "Arc_Line1_State_Last"
procNames(1)  = "Line1_Operator":   intNames(1)  = "Arc_Line1_Operator_Last"
' ... populate the remaining 38 entries ...

Dim tNow, success
tNow = Now

' --- Step 1: bulk read process tags ---
Dim tsProc, tsInt
Set tsProc = HMIRuntime.Tags.CreateTagSet
Set tsInt  = HMIRuntime.Tags.CreateTagSet
tsProc.Add procNames
tsInt.Add  intNames

Dim rcProc, rcInt
rcProc = tsProc.Read   ' returns HRESULTS; non-zero is failure
rcInt  = tsInt.Read

If (rcProc <> 0) Or (rcInt <> 0) Then
    HMIRuntime.Trace "Archive: bulk read failed rcProc=" & rcProc & " rcInt=" & rcInt & vbCrLf
    Exit Sub
End If

' --- Step 2: compare and queue changes ---
Dim i, curVal, lastVal, changed
changed = 0

Dim dsArchive
Set dsArchive = HMIRuntime.DataSet.Create("ARCHIVE_TEXT_CHANGES") ' UA table name

For i = 0 To TAG_COUNT - 1
    curVal = CStr(tsProc.GetTagValue(procNames(i)))
    lastVal = CStr(tsInt.GetTagValue(intNames(i)))

    If StrComp(curVal, lastVal, vbBinaryCompare) <> 0 Then
        ' Build a new UA row
        Dim row
        Set row = dsArchive.Add
        row.Value("TagName")  = procNames(i)
        row.Value("TagValue") = curVal
        row.Value("Timestamp") = tNow
        ' Quality default: 192 (Good). Adjust per OPC quality if needed.
        row.Value("Quality")  = 192

        ' Update mirror immediately to prevent double-write on next cycle
        tsInt(procNames(i)) = curVal  ' or use a name -> index map
        changed = changed + 1
    End If
Next

' --- Step 3: commit batched UA write + mirror write ---
If changed > 0 Then
    Dim rcUA, rcWrite
    rcUA = dsArchive.Write  ' buffered to UA engine
    rcWrite = tsInt.Write   ' persist internal mirror tags
    If rcUA = 0 And rcWrite = 0 Then
        HMIRuntime.Trace "Archive: wrote " & changed & " rows at " & tNow & vbCrLf
    Else
        HMIRuntime.Trace "Archive: write error rcUA=" & rcUA & " rcWrite=" & rcWrite & vbCrLf
    End If
Else
    HMIRuntime.Trace "Archive: no changes at " & tNow & vbCrLf
End If

Set dsArchive = Nothing
Set tsProc = Nothing
Set tsInt  = Nothing
%>

Key implementation notes:

  • Use StrComp(..., vbBinaryCompare) instead of = for text comparison. = in VBS is case-insensitive and may mask legitimate case changes.
  • Update the mirror tags before the User Archive write. If the UA write fails, the next cycle will detect the divergence and retry — at the cost of a duplicate on recovery, which is acceptable for audit data.
  • Always set Option Explicit at the top. Undeclared variables in WinCC VBS degrade to Variant and silently truncate on assignment.
  • Use HMIRuntime.Trace for diagnostics. Traces are written to WinCC_Sys_<ComputerName>.log in the project diagnostics directory.

7. C-Script Alternative (ANSI C, WinCC V7)

For higher throughput, especially when reading > 200 tags, use a C action. GetTagMultiWait reads all tags in a single call and blocks until the result is ready; the function is intrinsically bulk.

/* WinCC V7 C action — cyclic, 1 min */
#include "apdefap.h"

#define TAG_COUNT 40

static const char* PROC_NAMES[TAG_COUNT] = {
    "Line1_State", "Line1_Operator", /* ... 38 more ... */
};
static const char* INT_NAMES[TAG_COUNT] = {
    "Arc_Line1_State_Last", "Arc_Line1_Operator_Last", /* ... */
};

BOOL gscAction_archive_text(DWORD lParam)
{
    DWORD dwReadProc[TAG_COUNT];
    DWORD dwReadInt[TAG_COUNT];
    char  procVals[TAG_COUNT][256];
    char  intVals [TAG_COUNT][256];

    int rc;
    rc = GetTagMultiWait((DWORD)TAG_COUNT,
                         (LPCTSTR*)PROC_NAMES,
                         dwReadProc,
                         procVals,
                         sizeof(procVals[0]));
    if (rc != 0) { printf("PROC read failed %d\n", rc); return -1; }

    rc = GetTagMultiWait((DWORD)TAG_COUNT,
                         (LPCTSTR*)INT_NAMES,
                         dwReadInt,
                         intVals,
                         sizeof(intVals[0]));
    if (rc != 0) { printf("INT read failed %d\n", rc); return -1; }

    int changed = 0;
    for (int i = 0; i < TAG_COUNT; ++i) {
        if (strcmp(procVals[i], intVals[i]) != 0) {
            /* queue UA write for row PROC_NAMES[i] = procVals[i] */
            /* update mirror intVals[i] = procVals[i] */
            ++changed;
        }
    }
    if (changed > 0) {
        SetTagMultiWait((DWORD)TAG_COUNT,
                        (LPCTSTR*)INT_NAMES,
                        intVals,
                        sizeof(intVals[0]));   /* persist mirrors */
    }
    return 0;
}

GetTagMultiWait/SetTagMultiWait are documented in the WinCC Information System under ANSI C function descriptions → Tag functions. They are RT-safe and use the scheduler's bulk-read path, which is roughly 5-10× faster than calling GetTagChar in a loop for 40+ tags.

8. Trigger Configuration and Project Properties

Open Computer Properties → Runtime and verify:

  • Maximum number of actions: 5000 (default) — the recommended pattern uses 1, leaving headroom for 4,999 future actions.
  • Action processing priority: leave at default. WinCC's action scheduler runs in its own thread at high priority; lowering it stalls the whole archive.
  • Cycle time of the action: 1 min (or 30 s) — do not go below 10 s for text tags, or the WinCC scheduler will be polled faster than the OPC subscription can settle, causing duplicate writes when the same value is reported twice by the PLC.

Configure the trigger by opening the action → Properties → Trigger. Add a single Cyclic trigger with the desired period. Do not mix cyclic and tag triggers on the same action — the action will fire whenever any trigger fires, defeating the batching benefit.

9. Performance Optimization

For projects in the 100-500 tag range, apply these optimizations:

  1. Coalesce reads. Never call GetTagChar/SetTagChar in a loop. Use the Multi variants or the VBS TagSet for every read/write of more than ~8 tags.
  2. Write in batches. Use DataSet.Write (VBS) or build an in-memory array and call UAInsert with a WHERE-filtered statement (C). One bulk write of 40 rows is ~40× faster than 40 individual writes.
  3. Tune the cycle period. If the plant's fastest text-tag transition is on the order of 30 s, set the cycle to 30 s. If transitions are at human speeds (operator name, recipe ID), 1-5 min is sufficient and reduces the action's contribution to the overall CPU load to negligible.
  4. Bound the mirror array. If the tag list is dynamic, store it in a User Archive configuration table (not the data table) and read it at the start of the action. This avoids re-deploying the project when the operator needs to add one more audit tag.
  5. Skip the compare on first cycle. Use a project-side boolean internal tag Arc_Initialized. On the first execution, copy all process values into the mirror tags and skip the UA write. This prevents a startup flood of 40 inserts on every RT restart.

10. Verification and Commissioning

After deploying the action, verify the following in WinCC Runtime:

  1. Open Diagnostics → Trace and confirm the cyclic message "Archive: wrote N rows at HH:MM:SS" appears once per cycle.
  2. In the User Archive editor, connect to the runtime archive and confirm rows are added when you change a process tag in simulation.
  3. Force a value change in the PLC (or via the tag simulator). The next cycle should produce one new UA row with the new value and the cycle timestamp.
  4. Restart WinCC Runtime. The first cycle should log "no changes" or, if Arc_Initialized is not used, exactly 40 rows (one per tag, the post-startup values). After that, only true transitions should be logged.
  5. Open the SQL view CCUAHole_VIEW_<ArchiveName> in SQL Server Management Studio and run: SELECT TagName, COUNT(*) FROM dbo.CCUAHole_VIEW_ARCHIVE_TEXT_CHANGES GROUP BY TagName; The result should match the per-tag change distribution you generated during the test.

If a tag is missing from the result set, confirm that the tag name in the array matches the runtime tag name exactly — including case and any namespace prefix (e.g. AS1:: in WinCC Professional).

11. Troubleshooting Matrix

Symptom Likely Cause Verification Resolution
No rows written even after tag change Mirror tag not updated; or mirror tag name does not match intNames array Read Arc_Line1_State_Last in the tag browser — does it match the process tag? Fix the array entry; redeploy
Every cycle writes 40 rows Arc_Initialized not set, or mirror array written with empty values Check GetTagMultiWait return codes; verify dwReadProc contains 0 for all entries Add initialization branch; or check that the tag names are valid in the project
Cycle timeouts in APDIAG log UA write blocks longer than the scheduler period Search APDIAG for the action name + "timeout" Increase cycle period, or move the UA write to a separate, lower-priority action
Duplicate rows on RT restart Mirror tags lose their values on RT shutdown (default) Read Arc_Line1_State_Last before and after RT restart Set mirror tags to "Persistent" in Tag Properties, or re-implement the initialization branch
Action not loaded Project's "Maximum number of actions" reached, or compile error Open Global Actions; right-click the action → Compile Increase the limit, or fix the syntax error indicated
VBS Type mismatch on row.Value(...) Mirror tag is binary, not text Open Tag Properties → Type Cast via CStr() or change the tag type to Text tag 16-bit character set

12. Field-Proven Caveats

  • Text-tag character set. WinCC V7 internal text tags are 16-bit Unicode. PLC tags from S7-300/400 are typically 8-bit. Cast at the boundary with CStr() (VBS) or swprintf (C) or you will see garbage in the mirror.
  • WinCC Professional vs V7. The TagSet object exists in both, but the DataSet object is V7-only. In TIA Portal use HMIRuntime.Tags for reads and direct OLE-DB / SQLite calls for the archive.
  • Redundancy. In a redundant WinCC Server pair, the action runs on the preferred server. Mirror tags must be marked Project-wide (not computer-local) so the standby server can take over without a re-initialization flood.
  • OPC UA subscriptions. If the source is an OPC UA server, the cyclic 1-min action adds to the subscription latency, not replaces it. The subscription will still report changes as they occur; the cyclic action is the persistence boundary.

Frequently Asked Questions

What is the maximum number of tag-change triggers per action in WinCC V7?

Up to 250 tag triggers can be assigned to a single action. Beyond that, the action list becomes unmanageable and the scheduler evaluation slows down — split into multiple actions only if you also need to dispatch different logic per tag group.

Why prefer a cyclic action over per-tag event triggers for 40 text tags?

A single cyclic action with GetTagMultiWait reads all 40 tags in one bulk call, compares them to mirror tags, and writes only the changed rows in one batched User Archive write. Per-tag triggers fire 40 scheduler entries and 40 stack frames, with no batching benefit. The cyclic pattern is ~5-10× faster at runtime and much easier to maintain.

How do I prevent duplicate rows when WinCC Runtime restarts?

Mark the mirror internal tags as Persistent in Tag Properties, or add a first-cycle initialization branch that copies all current process values into the mirror tags and skips the User Archive write on the very first execution of the action.

What is the difference between GetTagMultiWait and the VBS TagSet?

GetTagMultiWait is the ANSI C function that blocks until all tags are read; the VBS TagSet is the COM-based equivalent accessed via HMIRuntime.Tags.CreateTagSet. Performance is comparable; C is preferred for >200 tags or when the action must run in a hard real-time context, VBS is preferred for readability and quick maintenance.

Can the same pattern be used in WinCC Professional (TIA Portal)?

Yes — the TagSet-based read logic is identical. The User Archive part must be replaced with direct SQL access (SQLite for WinCC Professional, SQL Server for V7) because the V7-only HMIRuntime.DataSet object is not available in TIA Portal runtime.

Back to blog