Overview
Many Siemens HMI applications require operator messages that surface only when the controller asks for them. The classic approach—hard-wiring visibility animations to dozens of discrete bits—does not scale beyond a handful of alarms and forces an HMI recompile every time a new message is added. A cleaner pattern, supported natively in WinCC Unified (TIA Portal V17 and later, including the V18/V19 releases), is to expose a single integer tag from the PLC and let a Unified JavaScript script interpret its value to open, swap, or close a popup window.
This article documents a production-grade implementation of that pattern, including:
- A scalable
A01-Message,A02-Message… naming convention so the controller can request any message by number. - A loop that closes any existing "Message" popup before opening a new one—eliminating the need to send a dedicated close command from the PLC.
- A sentinel value (
255) used by the PLC to force-close every open Message popup. - Two execution hosts for the script—an invisible object on the active screen and a Scheduled task—with a discussion of when to prefer each.
- Commissioning, verification, and a troubleshooting matrix that covers the common failure modes seen in the field.
The scripting surface used here is the WinCC Unified "Global Scripting" JavaScript API, documented in the TIA Portal help under WinCC Unified - Scripting (JavaScript). See the TIA Portal V18 WinCC Unified Help for the authoritative reference.
Prerequisites
| Item | Required Value | Notes |
|---|---|---|
| TIA Portal | V17 Update 4 or later (V18 / V19 recommended) | WinCC Unified JavaScript API matured in V17. |
| WinCC Unified Runtime | RT ≥ V17, PC or Unified Comfort Panel (MTP/MTP Pro) | Allows the global UI object and SysFct namespace. |
| PLC tag type |
Int (16-bit signed, range –32768…32767) |
Unsigned 0…255 is sufficient; a signed 16-bit tag is fine because the script only tests 0…255. |
| PLC ↔ HMI connection | S7-1500 / S7-1200 HMI connection active; tag visible in Unified tag table | Use "HMI tag" with acquisition mode Cyclic continuous, 1 s. |
| Pop-up screens | One screen per message number, named A01-Message, A02-Message, … |
Place under ~/Screen in the project tree. |
Architecture and Data Flow
The complete control loop is intentionally small:
- The PLC writes an integer to a configured HMI tag (e.g.
Numero_Page_Actuelle) each time it wants a new message displayed. - The HMI runs a JavaScript on a 1 s schedule. The script reads the tag via
Tags("Numero_Page_Actuelle").Read(). - If the value is in the range 1…254, the script composes the popup screen name
A<value>-Messageand callsUI.SysFct.OpenScreenInPopup(...). - If the value is exactly
255, the script iterates overUI.PopupScreenWindowsand closes any open window namedMessage. - If the value is
0, the script does nothing—useful as a "neutral" state the PLC can hold when no message is active.
Step-by-Step Implementation
1. Create the PLC Tag
In the S7-1500 / S7-1200 project, declare a tag (e.g. "DB_HMI".Numero_Page_Actuelle) of type Int. Wire it wherever your application logic needs to surface a message. The convention used in the source is:
-
0= no message -
1…254= display popupA01-Message…A254-Message -
255= force-close any open Message popup
2. Expose the Tag to the HMI
In the Unified project, create an HMI tag of the same name (e.g. Numero_Page_Actuelle) and connect it to the PLC tag through the standard HMI connection. Acquisition mode: Cyclic continuous, 1 s. Make sure the connection is online before testing.
3. Create the Popup Screens
Under Project tree ▸ Screens, create one screen per message number. Name them exactly:
A01-Message
A02-Message
A03-Message
...
A99-Message
These screens are normal WinCC Unified screens with whatever layout you need (text, iconography, Acknowledge / Cancel buttons). They have no special configuration beyond their name.
4. Create a Host Screen with an Invisible Script Object
On your main runtime screen (or any screen that stays loaded), place a small rectangle object and set its Visibility animation to 0 (always hidden). Attach the script shown in §5 to the Miscellaneous ▸ Events property of this invisible object, or, more commonly, to a Scheduled task that fires every second.
5. Create the Script
Create a new JavaScript file in Project tree ▸ Scripts ▸ Global modules (or a local script on the invisible object). Paste the following annotated implementation:
// ----------------------------------------------------------
// Dynamic popup controller for WinCC Unified
// Reads an integer tag written by the PLC and opens the
// popup screen whose name matches "A<value>-Message".
// Tag value 255 closes any open "Message" popup window.
// ----------------------------------------------------------
// Local alias to the global UI root. Always assign once and
// reuse the local "ui" reference; some runtime revisions
// have shown race conditions when UI is accessed directly.
let ui = UI;
// Read the integer tag the PLC controls.
let Prod_MsgVis = Tags("Numero_Page_Actuelle").Read();
// Fixed popup window geometry. Adjust to suit your layout.
let left = 290; // X position in pixels relative to parent
let top = 65; // Y position in pixels relative to parent
if (Prod_MsgVis > 0 && Prod_MsgVis < 255) {
// --- OPEN OR REPLACE A POPUP -----------------------------
// Close any existing "Message" popup first so that we
// never end up with two stacked popups of the same name.
for (let i = ui.PopupScreenWindows.Count - 1; i >= 0; i--) {
if (ui.PopupScreenWindows(i).Name === "Message") {
ui.PopupScreenWindows(i).Close();
}
}
// Build the screen name with two-digit zero padding so the
// PLC value 7 becomes "A07-Message" and not "A7-Message".
let screenName = "A" + Prod_MsgVis.toString().padStart(2, "0") + "-Message";
// OpenScreenInPopup signature:
// popupName, screenName, bModal, parentName,
// left, top, bCloseOnLostFocus, path
ui.SysFct.OpenScreenInPopup(
"Message",
screenName,
false, // non-modal: operator can still touch parent
"Message", // logical parent (matches popupName is OK)
left,
top,
false, // do not auto-close on focus loss
"~/Screen" // path to the popup screen
);
}
else if (Prod_MsgVis === 255) {
// --- FORCE-CLOSE ALL MESSAGE POPUPS ----------------------
// Iterate from the top of the collection; closing items
// while iterating forward corrupts indices.
let count = ui.PopupScreenWindows.Count;
for (let i = 0; i < count; i++) {
let j = count - i - 1; // reverse index
if (ui.PopupScreenWindows(j).Name === "Message") {
ui.PopupScreenWindows(j).Close();
}
}
}
// else: Prod_MsgVis == 0 → no action, no popup
6. Wire the Script to a Trigger
Two equally valid hosts are used in practice:
| Host | Pros | Cons |
|---|---|---|
| Invisible object on the active screen (event handler, e.g. Property change or Mouse hover — or simply polled every cycle if you bind to a tag that changes) | Script runs only when the screen is loaded; CPU is zero on unused screens. | Will not fire if the user navigates to a screen that does not contain the object. Use one persistent "background" screen (see note below). |
| Scheduled task under Project tree ▸ Scheduled tasks, 1 s, cyclic | Runs regardless of which screen is active; deterministic timing. | Adds a small CPU load on Comfort Panels even when no popup is needed. |
7. Add a Manual Re-Open Helper (Optional)
Operators sometimes close a popup by clicking the window's X without acknowledging. To re-open the most recent message, place a button anywhere on the main screen with this minimal script:
// Re-open the popup corresponding to the current tag value.
// Useful after an operator has dismissed a popup manually.
let Prod_MsgVis = Tags("Numero_Page_Actuelle").Read();
let screenName = "A" + Prod_MsgVis.toString().padStart(2, "0") + "-Message";
let ui = UI;
ui.SysFct.OpenScreenInPopup(
"Message", screenName, false, "Message",
290, 65, false, "~/Screen"
);
Script Breakdown and Key Engineering Details
Why let ui = UI;?
The runtime exports the global UI object as a property of the script context. Some Unified builds and some scheduled-task runners have produced intermittent "UI is not defined" errors when the script body uses UI directly—especially inside arrow functions or asynchronous callbacks. Capturing a local reference first is a defensive habit and is widely recommended on the WinCC Unified Scripting Reference.
Why a Reverse Index for Closing
Closing a window while iterating a for loop over the same collection shifts the indices of all later elements. Two patterns avoid the bug:
- Iterate backwards (
i = Count - 1; i >= 0; i--). - Snapshot the count and access items by reverse index (
count - i - 1).
Both forms are shown in the script for clarity; in production code, pick one and stay consistent.
The padStart(2, "0") Trick
Unified screen names are case-sensitive and must match the filename exactly. A PLC value of 7 would otherwise produce A7-Message, which does not exist if your screens are A07-Message. String.prototype.padStart guarantees the two-digit form, so values 1…9 become 01…09 and the lookup is uniform up to 99. If you need more than 99 messages, switch to three-digit padding (padStart(3, "0")) and rename your screens A001-Message … A254-Message.
Modal vs. Non-Modal
The third argument of OpenScreenInPopup is a boolean modal flag:
-
false(used here): operator can still interact with the parent screen—useful for "informational" messages that should not block. -
true: parent screen is dimmed and disabled until the popup is acknowledged. Use this for safety-relevant messages, but be aware that the user cannot navigate away or press an emergency stop on the parent if the popup hangs.
Parameter Mapping for SysFct.OpenScreenInPopup
| Argument Position | Parameter | Type | Meaning |
|---|---|---|---|
| 1 | popupName |
String | Unique name of the popup window instance. Re-using a name replaces the previous instance, which is why the loop-close in the script is technically redundant for the "swap" case but still required for the "255 close" case. |
| 2 | screenName |
String | Name of the screen to load inside the popup. |
| 3 | bModal |
Boolean |
true = modal, false = non-modal. |
| 4 | parentName |
String | Name of the parent window for modal context. |
| 5 | left |
Int | Left position in pixels, relative to parent. |
| 6 | top |
Int | Top position in pixels, relative to parent. |
| 7 | bCloseOnLostFocus |
Boolean | Auto-close when the popup loses focus. |
| 8 | path |
String | Folder path inside the project. Use "~/Screen" for the default Screens folder. |
Verification and Commissioning
- Compile the Unified project and start the runtime with the HMI connection online.
- From the PLC online watch table, force
Numero_Page_Actuelle = 1. The popupA01-Messagemust appear within one tag-acquisition cycle (≤ 1 s). - Force
Numero_Page_Actuelle = 2. The first popup must close andA02-Messagemust open in its place, again within one cycle. - Force
Numero_Page_Actuelle = 255. The open popup must close; the next0write must leave the screen clear. - Force
Numero_Page_Actuelle = 0for ten seconds and verify that no popup appears and CPU remains nominal. - Test the manual re-open button from §7: open
A01-Message, click the X to dismiss it manually, click the re-open button, and confirm the popup returns.
Troubleshooting Matrix
| Symptom | Likely Root Cause | Fix |
|---|---|---|
Popup never opens; Tags(...).Read() returns the wrong value |
Tag is not connected, or the HMI tag name does not exactly match the PLC tag name (case-sensitive in Unified) | Check the Unified tag in the HMI tag table; confirm connection status is green and the acquisition cycle is set |
| Popup opens the first time, never closes | Value 255 is never sent; PLC only writes the new value and never resets | Add a one-shot reset in the PLC: after the script has had time to react, write 0 back to the tag, or use a separate CloseMsg trigger |
| Two popups stack on top of each other | Forward iteration over PopupScreenWindows shifts the index after the first .Close()
|
Use the reverse-index pattern shown in §5 |
| "UI is not defined" in the script log | Direct access to the global UI object inside a closure or scheduled task |
Capture let ui = UI; at the top of the script and use ui thereafter |
| Screen not found / blank popup | PLC value produced a screen name that does not exist (e.g. value 12 → "A12-Message" exists, but a PLC value of 5 with no padStart → "A5-Message" does not) |
Verify the padStart(2, "0") call and ensure the screen file is named to match exactly |
| Script never runs at all | Invisible-object host is on a screen the operator can navigate away from | Move the object to a screen that is always loaded (start screen, global area, or background screen) or switch to a Scheduled task |
| Modal popup blocks the operator during alarm storms |
bModal = true on a high-frequency alarm |
Use false for informational messages; reserve true for safety-relevant ones |
| CPU load on Comfort Panel rises > 30 % | Scheduled task is firing faster than 1 s, or dozens of popups are open simultaneously | Reduce the schedule to 1 s; verify the close-loop removes stale popups; check PopupScreenWindows.Count in a debug script |
Design Notes and Edge Cases
Maximum message count: With padStart(2, "0") the script supports 1…254 distinct screens. To go beyond 254, change the padding to 3 digits, add screens A001-Message … A<N>-Message, and reduce the sentinel value to something that fits the range, e.g. 9999 as "close all" while keeping the comparison Prod_MsgVis < sentinel.
Race between PLC write and script read: Because the script runs on a 1 s schedule, a tag value that changes twice within one second can be missed. If your PLC logic toggles the tag rapidly (write message, wait, write 0), insert a Ton timer of at least 1.2 s between the two writes.
Redundant close on swap: Strictly speaking, the pre-close loop is not required when the PLC writes a different valid message number—Unified will replace a popup that uses the same popupName. The loop is, however, mandatory when the PLC sends 255 (force-close) and is also defensive against orphaned popups from a previous runtime session. Keep it in.
Why the sentinel 255? It is the maximum unsigned 8-bit value and a common "invalid / no-data" marker in legacy Siemens code. The script treats it explicitly, so 0…254 is the safe range for actual messages and 255 is reserved for close. If you need the full 0…255 range for messages, switch the sentinel to -1 (or any out-of-range value) and update the comparison accordingly.
Localized messages: Because the PLC drives the message number and the HMI selects the screen, you can ship the same PLC code with completely different screen libraries per language. The translation work is done once in the HMI project, not in the PLC.
FAQ
Does the script run on every screen, or only the active one?
It depends on the host. A scheduled task runs globally every cycle. An invisible object only runs while the screen that contains it is loaded. For an always-on behavior, place the object on a permanent background screen or use a scheduled task.
Why is the screen name padded to two digits?
Unified screen names are case-sensitive and must match exactly. padStart(2, "0") converts values 1…9 into 01…09, so the constructed string always matches the A01-Message … A99-Message file naming convention.
Can I use 255 as a regular message number?
No—value 255 is the script's sentinel for "close every open Message popup." Use any other unused value, or change the sentinel to -1 and update the comparison.
How do I prevent two popups stacking on top of each other?
Iterate UI.PopupScreenWindows in reverse and close any window whose Name equals "Message" before calling OpenScreenInPopup with the same popupName. The example script in §5 implements this exactly.
What acquisition cycle should I use for the HMI tag?
1 s is the standard for operator messages on WinCC Unified. Shorter cycles (250 ms) add CPU load without perceptible benefit; longer cycles (2 s) make the response feel sluggish to operators.