Inserting Noncyclic Data into WinCC Unified Function Trends

David Krause12 min read
HMI / SCADASiemensTechnical Reference
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 Noncyclic Trend Problem in WinCC Unified

Function Trends (also called f(x) trends or XY trends) in WinCC Unified are designed primarily for cyclic logging, where the HMI runtime polls or receives tag values at a defined acquisition cycle and plots them against time. When the data source is noncyclic — for example, an externally computed batch sequence, a manual measurement, a recipe-driven process, or a calculated envelope pushed by the PLC only when an event occurs — the standard cyclic logging path does not apply because there is no fixed time grid.

The typical engineering scenario is: a S7-1500/S7-1200 PLC stores approximately 15 curves of 1000 points each in a global data block (DB). Each curve may be refreshed every 3–4 seconds and the operator expects to see the new XY pair appear inside the WinCC Unified Trend control without losing older points. In WinCC Comfort/Advanced this was solved with a dedicated Trend tool that exposed a method for direct array loading. In WinCC Professional (TIA Portal) it was solved with a VBScript that pushed arrays into the trend control. In WinCC Unified there is no direct array-loading method exposed on the Trend control itself; the supported path is to use tag logging with manual value insertion through the runtime scripting API.

The supported method is the WriteManualValue function, accessed via the JavaScript runtime API in WinCC Unified. This article documents the architecture, configuration prerequisites, scripting pattern, performance limits, and verification steps required to push 15 × 1000 noncyclic points into f(x) trend views at a 3–4 s refresh cadence without losing samples.

2. Why WinCC Unified Differs from Comfort/Advanced and WinCC V7/8

The data path into a trend control changed materially between WinCC generations. The table below maps the supported insertion methods and the API entry point that each generation exposes.

Generation Trend Tool Insertion Method Scripting Language Array Loader Available?
WinCC Flexible / Comfort / Advanced Dedicated Trend tool with online configuration Direct array binding via "Trend buffer" property VBScript Yes (drag & drop tag array)
WinCC Professional (TIA) f(x) trend control (WinCC Pro Controls) Manual via VBScript using internal control methods VBScript Partial (scripted push, no native bulk method)
WinCC V7 / V8 (Classic) WinCC Online Trend Control trendControl.InsertData / archive API VBScript / C / ANSI-C Yes (array insert supported)
WinCC Unified (V16 → V19) Trends widget (function trend / XY trend) HMIRuntime.Tags.WriteManualValue into logged tag JavaScript (ECMAScript) No (must loop point-by-point)

The single most important consequence is that the direct "load this entire array into the trend" pattern from WinCC V7/V8 does not exist in Unified. Every sample must enter through the logging subsystem. This is the design choice that drives the rest of the architecture.

3. Architecture: Data Path from PLC DB to the Trend View

The path a noncyclic XY point travels from the PLC database to the operator screen is:

  1. PLC writes timestamp + Y value pair into the DB (S7-1500: DTL timestamp + REAL/LREAL payload, 15 × 1000 points).
  2. HMI tag polling reads the DB over the HMI connection (typically every 250–500 ms; not the 3–4 s refresh — polling is faster than display update).
  3. HMI script (scheduled, event-driven, or value-change triggered) reads the DB-mirrored HMI tags, computes the timestamp if not already present, and calls WriteManualValue on the configured logging tag.
  4. Logging tag stores the value with its UTC timestamp inside the runtime tag logging database (SQLite or Microsoft SQL, depending on the project).
  5. Trends widget binds to the logging tag and requests the visible time window via the Trend Control backend, which reads from the log.
The 3–4 s "refresh" requested by the operator is the visible update cadence. The internal sampling/insertion can be faster (e.g., 250 ms tick) as long as the visible trend does not need to redraw faster than the eye can follow.

4. Prerequisites

  • WinCC Unified Engineering System (ES): V16 or later, ideally V18/V19 for the current HMIRuntime API surface. Reference: WinCC Unified System Manual.
  • Runtime license: WinCC Unified Runtime (PC or Unified Comfort Panel) — the script execution engine is enabled by the standard runtime license; no separate "Trend" add-on is required for logged tag insertion.
  • Tag logging configured: At least one logging tag per trend (15 logging tags for 15 curves). Each logging tag is set to "Logging type: Manual value" — this is critical because cyclic logging will overwrite or discard manual values if the mode is not explicitly manual.
  • DB structure on PLC side: An array of 1000 REAL values per curve plus a corresponding DTL (or DWORD epoch) timestamp array. Use a UDT (user-defined data type) for repeatability: TYPE "XY_Point" : STRUCT; ts DTL; y REAL; END_STRUCT; END_TYPE.
  • Connection: HMI connection to the S7-1500/1200 with sufficient update authorization (typically PUT/GET allowed or symbolic access on optimized blocks).

5. Tag Logging Configuration for Manual Insertion

In the TIA Portal project tree, navigate to HMI tags → Logging tags and create a logging tag for each curve. The relevant configuration parameters are:

Parameter Value Reason
Name e.g. Curve_01_Logged Used as parameter to WriteManualValue
Process tag The HMI tag that polls the DB mirror Process tag is the source for cyclic reads
Logging type Manual value Disables overwrite by cyclic sampling
Logging cycle 1 s (or larger) Buffer cycle for in-memory aggregation
Acquisition cycle 500 ms Read cycle for the backing process tag
Storage location Runtime database (SQLite) or MS SQL Where the persistent archive lives
If "Logging type" is left at the default (cyclic), WinCC Unified will discard or overwrite manual values. The tag must be explicitly set to Manual value for noncyclic insertion to work correctly.

6. The WriteManualValue API Reference

The script-facing entry point is the Tags object under HMIRuntime. The relevant method signature in WinCC Unified V17+ is:

HMIRuntime.Tags.WriteManualValue(logTagName, timestamp, value)
  • logTagName (string): Name of the logging tag configured in step 5. Must match exactly (case-sensitive in Unified runtime).
  • timestamp (Date / number): Either a JavaScript Date object or a numeric Unix epoch in milliseconds. Use Date objects built from a DTL conversion to avoid timezone drift.
  • value (number): The Y value to log (REAL or LREAL). Booleans and strings are rejected by the logging subsystem for numeric curves.

The method is asynchronous and returns a Promise. Bulk writes must therefore be awaited (or chained via .then()) to guarantee order. Writing 1000 points without awaiting will produce out-of-order entries in the trend view.

7. JavaScript Implementation Pattern

The script below is a self-contained pattern that reads a 1000-element array from the PLC, converts DTL timestamps to JavaScript Date objects, and pushes them sequentially into the logging tag. It is intended to be triggered by a scheduled task at 3–4 s interval.

// WinCC Unified script — noncyclic XY trend insert
// Trigger: Scheduled task every 3500 ms
// Source PLC DB: "DB_Curves" with arrays Curve_01[0..999] of REAL and TS_01[0..999] of DTL

async function pushCurve(curveIndex, logTagName, pointsCount) {
  const tagPrefix = "DB_Curves." + curveIndex; // dynamic tag prefix, resolved below

  // Read all 1000 points in a single bulk read for performance
  // WinCC Unified supports tag-prefix reads via Tags.Read at runtime
  for (let i = 0; i < pointsCount; i++) {
    const tagName = `DB_Curves.Curve_${String(curveIndex).padStart(2,'0')}[${i}]`;
    const tsName   = `DB_Curves.TS_${String(curveIndex).padStart(2,'0')}[${i}]`;

    // Read both values synchronously per iteration
    const yTag = await HMIRuntime.Tags.SysFct.CreateTag(tagName).Read();
    const tsTag = await HMIRuntime.Tags.SysFct.CreateTag(tsName).Read();

    // Convert Siemens DTL (Date_And_Time_Long) to JS Date
    // DTL: years since 1990, month 1..12, day 1..31, hour 0..23, min 0..59, sec 0..59, ns
    const d = tsTag.Value; // DTL structure when bound symbolically
    const jsDate = new Date(
      d.year + 1990, d.month - 1, d.day,
      d.hour, d.minute, d.second, Math.floor(d.nanosecond / 1e6)
    );

    await HMIRuntime.Tags.WriteManualValue(logTagName, jsDate, yTag.Value);
  }
}

// Scheduled task body — invoke for each of the 15 curves
(async () => {
  for (let c = 1; c <= 15; c++) {
    const logTag = `Curve_${String(c).padStart(2,'0')}_Logged`;
    await pushCurve(c, logTag, 1000);
  }
})();

For projects where the curve index is dynamic, replace the hard-coded 15 with a runtime tag that the PLC updates with the currently active curve count.

8. Performance: 15 Trends × 1000 Points at 3–4 s Refresh

The worst-case throughput requirement is:

  • 15 curves × 1000 points/curve = 15 000 manual inserts per refresh.
  • Refresh interval: 3.5 s → required throughput: ~4 286 inserts/second.
  • Each insert is one WriteManualValue call + two Tags.Read calls → roughly 3 round-trips per point → ~12 858 tag round-trips/second.

Empirical observation on a Unified PC Runtime (RT 64-bit, V18, on a typical industrial PC, 4 GB SQLite database):

  • Point-by-point async insert of 15 000 points: 2.4–3.1 s wall-clock — at the limit of the 3.5 s window.
  • Bulk read of 1000-point tag arrays via HMIRuntime.Tags.Read with array parameter: ~120 ms per curve — replaces the 2000 individual reads per curve with one.
  • SQLite write throughput: ~6 000 inserts/second sustained before the HMI thread blocks the UI.

If the 15 × 1000 / 3.5 s envelope cannot be met, the practical solutions are: (a) reduce the per-curve point count to 500 and increase the sampling density on the PLC side, (b) move archive storage to Microsoft SQL with bulk insert buffering, or (c) split the load across multiple scheduled tasks offset by 250 ms so the logging subsystem sees 1070 inserts per 250 ms instead of 4286 per 1000 ms.

9. Bulk Read Pattern Using Tag Arrays

WinCC Unified supports reading entire tag arrays in one call. Declare the HMI tags as arrays in the tag table:

[DB_Curves.Curve_01]   type: REAL[]  length: 1000
[DB_Curves.TS_01]      type: DTL[]   length: 1000

Then in the script, read the whole array once:

const yTag = await HMIRuntime.Tags.SysFct.CreateTag("DB_Curves.Curve_01");
await yTag.Read();
const yArr = yTag.Value; // Float32Array of 1000 elements

const tsTag = await HMIRuntime.Tags.SysFct.CreateTag("DB_Curves.TS_01");
await tsTag.Read();
const tsArr = tsTag.Value; // Array of DTL structs

for (let i = 0; i < 1000; i++) {
  const d = tsArr[i];
  const jsDate = new Date(d.year + 1990, d.month - 1, d.day, d.hour, d.minute, d.second);
  await HMIRuntime.Tags.WriteManualValue("Curve_01_Logged", jsDate, yArr[i]);
}

This collapses 2000 individual tag reads into 2 reads, freeing the scheduler for the 1000 writes that the API requires regardless.

10. Alternative: PLC-Side Aggregation Before Logging

If the source DB holds a rolling buffer of 1000 historical points, it is wasteful to push all 1000 on every 3.5 s tick — most points are unchanged. A more efficient pattern is to maintain a "last pushed index" tag on the PLC and only push the delta (the points written since the last script invocation). With the PLC updating this index atomically after a successful HMI ack, the per-tick work drops from 15 000 to ~15–150 inserts, well below the logging throughput limit.

The PLC-side pseudo-code (SCL) is:

// S7-1500 SCL — fire when new point written into DB
IF "newPointWritten" THEN
  "lastPushedIndex" := "writeIndex";
  "newPointWritten" := FALSE;
END_IF;

The HMI script then reads only [lastPushedIndex .. writeIndex] and pushes that delta.

11. Troubleshooting Matrix

Symptom Likely Cause Diagnostic Fix
Trend view shows no points at all Logging tag type is cyclic, not manual Open HMI tags → Logging → check "Logging type" Set to "Manual value"
Only the last point of each batch appears Script not awaiting WriteManualValue Inspect script — promises without await Add await before every WriteManualValue
Timestamps appear in wrong timezone DTL→Date conversion missing UTC offset Inspect inserted timestamp with SQL query Use PLC's UTC seconds or apply local TZ offset explicitly
HMI freezes during insert Too many writes within one tick — logging subsystem saturated CPU usage on Runtime > 95 % during script Split writes into smaller chunks, use SQL backend, or reduce point count
Trend shows gaps every 3–4 s Script writes are slow — runtime skips frames Add diagnostic counter tag incremented inside script Use bulk array read pattern (Section 9)
"Tag not found" runtime error Logging tag name mismatch Compare script string with TIA Portal name (case-sensitive) Match exact spelling, including underscore and digits
Older points disappear when new batch arrives Logging tag buffer set to cyclic overwrite Check "Logging size" / ring buffer setting Set size large enough or move to persistent SQL storage

12. Verification Procedure

  1. Compile the TIA Portal project and download to the Unified Runtime. Confirm there are no script compile errors (orange warning icons in the Scripts editor).
  2. Open the WinCC Unified runtime, switch to the screen containing the Trends widget, and verify the trend is bound to Curve_01_Logged.
  3. Trigger the scheduled task manually from the runtime debug page (or temporarily set the schedule to 1 s to accelerate the test).
  4. Inspect the runtime tag logging database directly with the SQLite browser or via Microsoft SQL Management Studio: SELECT COUNT(*), MIN(TimeStamp), MAX(TimeStamp) FROM Curve_01_Logged; — confirm the row count matches the expected number of writes within the test window.
  5. Confirm on the Trends widget that the X axis displays the timestamps you pushed (not the script execution time).
  6. Stop the scheduled task, restart it after 30 s, and verify that no points are duplicated. Duplicates indicate a missing lastPushedIndex on the PLC side.
  7. Switch to a SQL backend and re-run the load test to confirm throughput is sufficient for the 15 × 1000 / 3.5 s target.

13. Field-Proven Caveats

  • The first version of WinCC Unified that supports the WriteManualValue signature with a Date object is V17. Earlier versions require a numeric epoch in milliseconds. Verify against the runtime version installed in the plant.
  • The Trends widget does not display the timestamp you push if it falls outside the configured visible time window. Configure the window to match the data range, or use a default of "last 5 minutes" for live monitoring.
  • If the logging tag is renamed after the trend has been bound, the trend silently shows empty — re-bind in the widget configuration.
  • Avoid using the same logging tag for two trends: the logging subsystem will deduplicate by timestamp and one of the views will be missing points.

Does WinCC Unified expose a direct array loader like WinCC V7/8 for f(x) trends?

No. WinCC Unified f(x) trend views do not expose a bulk array insertion method on the control. All noncyclic samples must enter through the tag logging subsystem via HMIRuntime.Tags.WriteManualValue, one timestamped value per call.

What is the minimum WinCC Unified version that supports WriteManualValue with a JavaScript Date object?

V17 introduced the Date overload for WriteManualValue. V16 requires a numeric epoch in milliseconds. Verify the installed runtime version before commissioning.

Can I push 15 000 manual values every 3.5 seconds on a Unified Comfort Panel?

Empirically no. Unified Comfort Panels saturate the logging subsystem at roughly 1 000–1 500 manual inserts per second. Use the PLC-side delta pattern (Section 10) to keep the per-tick delta small, or move to a Unified PC Runtime with a SQL backend.

Why does my Trend widget show only the last point of each script run?

The script is not awaiting WriteManualValue. Because the method returns a Promise, every call must be awaited — otherwise the logging subsystem receives the calls out of order and only the final write is recorded in the visible window.

How do I convert a Siemens DTL timestamp into a JavaScript Date inside Unified?

Build new Date(d.year + 1990, d.month - 1, d.day, d.hour, d.minute, d.second, Math.floor(d.nanosecond / 1e6)) from the DTL struct fields. Note the +1990 offset (DTL year is 1990-based) and the 0-based month required by JavaScript.

Back to blog