WinCC AlarmControl Combining Date and Number Filters

David Krause12 min read
HMI / SCADASiemensTutorial / How-to
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. Problem Overview

Siemens WinCC AlarmControl ships with two demo C actions in the project template: one filters the active alarm view by message number (MSGNR range) and a second filters by date/time interval (DATETIME range). Each script calls SetPropChar(lpszPictureName, "Control1", "MsgFilterSQL", SQL) independently, and both work in isolation. When the user copies both bodies into a single project function or single C action and runs it from a button, the resulting alarm view shows the original unfiltered list instead of the intersection — i.e., the combined filter is ignored, the runtime filter accepts an empty or malformed string, or the action errors out silently.

This article documents why the merge fails in WinCC V6 SP3 through WinCC V7.5 SP2 (the same ANSI-C syntax applies; the VBS path diverges slightly) and shows a working combined action along with the parameter table, debug method, and verification procedure.

2. WinCC AlarmControl Filtering Architecture

The AlarmControl OCX exposed by WinCC accepts one SQL-style WHERE-clause fragment at a time through the MsgFilterSQL property. The control re-evaluates MsgFilterSQL each time the property changes and applies it to its underlying CCAlgMsgBuf archive query. According to the WinCC Information System help shipped with V7 and the Siemens WinCC V7.5 SP2 manual, the property accepts an unprefixed WHERE fragment (no leading WHERE keyword) referencing four pre-defined logical columns:

Logical Column Type Description
DATETIME datetime (sql_variant) Alarm event time stamp (UTC or local depending on archive configuration)
MSGNR integer Configured message number (1-65535)
STATE integer Alarm state bitmask (CAME, GONE, ACK, etc.)
PRIORITY integer Alarm priority (0-16)

The AlarmControl uses the message number and timestamp columns retrieved from the mslgview archive database view, which maps to either the AlgView view in the WinCC project database or, in larger systems, the central SQL Archive Server. The DATETIME literal must be single-quoted in the format 'YYYY-MM-DD HH:MM:00.000000000' — the 9-digit nanosecond mantissa matches the WinCC Round-File and SQL Archive Server datetime2 implementation. Microsoft SQL Server coerces the literal transparently; C-Script's sprintf must construct it manually.

3. Root Cause: Why Two Scripts Cannot Both Run

Three mechanical issues cause the combined action to fail. They are listed in the order they appear in the original post:

  1. String buffer under-sized. The local buffer char SQL[120] holds ~120 bytes. Each time/date predicate is ~50 bytes (the format string plus two integers), and two MSGNR predicates add ~25 bytes plus the AND separators. The merged string exceeds 120 bytes before the trailing null, so sprintf silently truncates the predicate and WinCC never receives a valid WHERE fragment.
  2. Tag ID collisions across #define blocks. Each C action declares // next TagID : 12 in its WINCC:TAGNAME_SECTION. When both bodies are pasted into one action, WinCC's compiler walks the section and assigns IDs sequentially. If both #define blocks reference the same starting ID, the second script overwrites the first; if a tag is referenced through the wrong ID at runtime, GetTagWord returns zero. The user can detect this with the WinCC Tag Simulator (Tools > Tag Simulator) attached to the action.
  3. Predicate concatenation order. The original time script builds "%s AND %s" with SQL_end first and SQL_start second. The result is "DATETIME <= ... AND DATETIME >= ...", which is logically valid but evaluates every row twice. When MSGNR predicates are appended naively as a third sprintf, MSGNR ends up outside the AND that the time block produces and the parser in the AlarmControl may collapse unmatched predicates.
The single most common cause in the field is buffer overflow on SQL[]. Increase it to char SQL[260] (or larger) for any combined filter and verify with the MSGWINCC output window during commissioning (see Verification section).

4. Prerequisites

  • WinCC V6 SP3, WinCC V7.0, V7.2, V7.3, V7.4, V7.5 (any edition that ships the AlarmControl OCX) — the C-script syntax with the four-section comment header is identical across these versions.
  • Configured alarm classes and message numbers 1-10 (or whichever range is required) in the Alarm Logging editor.
  • One WinCC picture containing an AlarmControl named Control1.
  • Twelve internal or process tags defined in WinCC Explorer > Tag Management: day_begin, month_begin, year_begin, hour_begin, minute_begin, day_end, month_end, year_end, hour_end, minute_end, MSGNumber_begin, MSGNumber_end. Each should be Unsigned 16-bit (WORD).
  • An I/O field pair to enter the day/month/year/hour/minute and the two MSGNR limits on the AlarmControl picture.

5. Build the Combined C Action

The combined action is a project function or a button-click C action. Replace "Control1" with the name of your AlarmControl instance and the text-input tag names if you have renumbered them.

// WINCC:TAGNAME_SECTION_START
// syntax: #define TagNameInAction "DMTagName"
#define v_day_begin       "day_begin"
#define v_month_begin     "month_begin"
#define v_year_begin      "year_begin"
#define v_hour_begin      "hour_begin"
#define v_minute_begin    "minute_begin"
#define v_day_end         "day_end"
#define v_month_end       "month_end"
#define v_year_end        "year_end"
#define v_hour_end        "hour_end"
#define v_minute_end      "minute_end"
#define v_MSGNumber_begin "MSGNumber_begin"
#define v_MSGNumber_end   "MSGNumber_end"
// next TagID : 13
// WINCC:TAGNAME_SECTION_END

// WINCC:PICNAME_SECTION_START
// syntax: #define PicNameInAction "PictureName"
#define v_Control1 "Control1"
// next PicID : 1
// WINCC:PICNAME_SECTION_END

WORD day_b, month_b, year_b, hour_b, minute_b;
WORD day_e, month_e, year_e, hour_e, minute_e;
WORD MSGNumber_b, MSGNumber_e;
char SQL_dt[80] = "";
char SQL_nr[80] = "";
char SQL[260]   = "";

day_b      = GetTagWord(v_day_begin);
month_b    = GetTagWord(v_month_begin);
year_b     = GetTagWord(v_year_begin);
hour_b     = GetTagWord(v_hour_begin);
minute_b   = GetTagWord(v_minute_begin);
day_e      = GetTagWord(v_day_end);
month_e    = GetTagWord(v_month_end);
year_e     = GetTagWord(v_year_end);
hour_e     = GetTagWord(v_hour_end);
minute_e   = GetTagWord(v_minute_end);
MSGNumber_b = GetTagWord(v_MSGNumber_begin);
MSGNumber_e = GetTagWord(v_MSGNumber_end);

sprintf( SQL_dt, "(DATETIME >= '%04d-%02d-%02d %02d:%02d:00.000000000' AND DATETIME <= '%04d-%02d-%02d %02d:%02d:00.000000000')",
         year_b, month_b, day_b, hour_b, minute_b,
         year_e, month_e, day_e, hour_e, minute_e );

sprintf( SQL_nr, "(MSGNR >= %u AND MSGNR <= %u)", MSGNumber_b, MSGNumber_e );

sprintf( SQL, "%s AND %s", SQL_dt, SQL_nr );

SetPropChar(lpszPictureName, "Control1", "MsgFilterSQL", SQL);
printf("\\r\\n[FILTER] %s", SQL);

6. Code Walkthrough

Block What it does
WINCC:TAGNAME_SECTION Registers twelve runtime tags used in the action. The // next TagID : N line MUST be the maximum tag ID + 1; set this to one above the last declared tag. The compiler uses this value to size the action's internal ID table.
WINCC:PICNAME_SECTION Registers the picture/object name used at runtime. Only Control1 is referenced; picture-name entries are required when the script is invoked from a Global Script action that needs to locate its host picture.
Local variables All WORD (unsigned 16-bit) to match WinCC tag I/O range. DWORD also works but is unnecessary for daily date/time values.
Three buffer sizes SQL_dt[80] holds one parenthesis-wrapped time predicate (~76 chars worst case). SQL_nr[80] holds the message-number predicate. SQL[260] contains the final combination plus a trailing null; 260 covers SQL Server MAX convenience without crossing into the next stack frame on legacy WinCC V6 builds.
sprintf time predicate Builds (DATETIME >= '…' AND DATETIME <= '…'). Note the outer parentheses: they protect the AND-pair from being absorbed by any filter-management wrapper added by the AlarmControl runtime.
sprintf message-number predicate Builds (MSGNR >= n AND MSGNR <= n). %u format specifier forces unsigned decimal, avoiding accidental signed wrap when MSGNR exceeds 32767 (WinCC supports up to 65535 configured messages).
Final sprintf Joins both parenthesised groups with a single AND. No outer WHERE keyword — the AlarmControl adds it internally.
SetPropChar Pushes the SQL fragment into the AlarmControl. Returns a BOOL; non-zero means the property was accepted. Always check this return value when debugging.
printf("\r\n[FILTER] %s", SQL); Writes the constructed filter into the WinCC diagnostic window (Global Script > Debug). Confirm the string is intact and well-formed before any alarm rows are evaluated.

7. MsgFilterSQL Syntax Reference

The following fragment grammar is supported by AlarmControl in WinCC V7.0+ (verified against the WinCC Information System help shipped with the V7.5 SP2 distribution):

MsgFilterSQL := Predicate | Predicate ('AND'|'OR') MsgFilterSQL
Predicate     := Column Operator Literal
Column        := 'DATETIME' | 'MSGNR' | 'STATE' | 'PRIORITY' | 'CLASSID' | 'TYPE' | 'AGID' | 'AGCT'
Operator      := '=' | '<>' | '<' | '<=' | '>' | '>=' | 'LIKE'
Literal       := integer | quoted_datetime | quoted_string

Additional columns commonly referenced in the field:

  • CLASSID — alarm class identifier, integer.
  • TYPE — alarm category (1=alarm, 2=warning, etc.).
  • AGID — AG/AS identification (used in distributed WinCC).
  • AGCT — counter within an AG.

The LIKE operator is useful for free-text searches on configured message text fields; it operates only if the alarm archive is SQL Server (not the legacy CS Archive). For text search, the appropriate column is TEXT1, TEXT2, etc., referenced through the MsgFilterSQL text-search extension; refer to the Siemens Industry Online Support entry "WinCC AlarmControl: SqlFilter for messages" for the precise grammar across all supported versions.

8. Alternative: VBScript Implementation (WinCC V7+)

Modern WinCC projects use VBS (VB Script) instead of ANSI-C for AlarmControl behaviour. The same combined-filter pattern translates to:

Dim dtBeg, dtEnd, msnrBeg, msnrEnd, sql
dtBeg   = DateSerial(Year(beginYear), beginMonth, beginDay) & " " & _
          Right("0" & beginHour, 2) & ":" & Right("0" & beginMinute, 2) & ":00"
dtEnd   = DateSerial(EndYear, EndMonth, EndDay) & " " & _
          Right("0" & EndHour, 2) & ":" & Right("0" & EndMinute, 2) & ":00"
sql = "(DATETIME >= '" & dtBeg & ".000000000' AND DATETIME <= '" & dtEnd & ".000000000') " & _
      "AND (MSGNR >= " & msnrBeg & " AND MSGNR <= " & msnrEnd & ")"
ScreenItems("Control1").MsgFilterSQL = sql

Key differences between the two implementations:

Aspect ANSI-C Action VBScript
Buffer control Manual char[] size Automatic (VB string object)
Setter SetPropChar(...,"MsgFilterSQL", SQL) ScreenItems("Control1").MsgFilterSQL = sql
Tag read GetTagWord() HMIRuntime.Tags(...).Read
Picture-name scope lpszPictureName parameter Resolved automatically inside the screen
Debug method printf to MSGWINCC HMIRuntime.Trace to APDiag

9. Verification Procedure

  1. Open the WinCC Graphics Designer picture containing the AlarmControl.
  2. Open a Global Script > Debug window (or APDiag in TIA Portal) and connect it to the RT process.
  3. Trigger the combined action (button click or hot-key if bound to an event).
  4. Verify the [FILTER] ... line appears in the debug window and that the string ends with a complete parenthesised expression. A truncated string confirms the buffer was again too small.
  5. Click inside the AlarmControl. Status bar should display the active filter count in the form n of m messages are shown.
  6. Enter day_begin=01 month_begin=01 year_begin=2024 hour_begin=00 minute_begin=01, matching the entire day's worth of alarms. The MSGNR range of 1-10 should also be applied. The intersection must be empty if no alarms 1-10 occurred on 1 Jan 2024 — that is the expected empty list, not an error.
  7. Confirm MsgFilterSQL clears correctly when an explicit empty string is assigned. Use SetPropChar(lpszPictureName,"Control1","MsgFilterSQL","") to reset.

10. Troubleshooting Matrix

Symptom Likely Root Cause Fix
Alarm list always shows everything (no filter applied) String buffer overflow truncated the predicate to empty or invalid SQL Increase SQL buffer to 260 or 512. Verify with printf in MSGWINCC
Filter applies with one click but not the next Stale tag values in I/O fields not committed Wire I/O fields to commit on 'Lost Focus' event, not 'Input Finished Only'
Filter never applies, MSGWINCC shows <empty> Action fired before AlarmControl was instantiated in the runtime Delay the call by 500 ms or trigger from OnPageActivate instead of OnOpen
Date filter appears to apply but shows wrong day User enters day/month backwards (locale issue) Bind day and month tags to separate I/O fields with explicit labels; add a reformat step that swaps if day > 12
MSGNR filter returns rows outside the configured range MSGNR column semantics: AlarmControl may interpret it as the runtime messaging number, not the configured number Open alarm logging editor and confirm the configured numbers match the runtime MSGNR; use the "Message Numbers" dialog in Alarm Logging to map
Debug output shows malformed string with stray quotes Single-quote in a non-standard datetime literal Standardise on the 9-fractional-digit format inside the predicate; the AlarmControl parser expects exactly that shape
Action returns "Function not found" at first run Tag IDs collide because // next TagID comment was not updated Recompile and re-check the TagName section header
Performance stalls when filter is wide (full day, all messages) AlarmControl reissues the SQL every property write; the full archive is scanned Reduce the time window before widening MSGNR; consider an indexed view in the SQL Archive Server if range regularly exceeds 500 k events

11. Performance & Long-Term Maintenance

The AlarmControl rebuilds its underlying view every time MsgFilterSQL is written. For an archive of < 100 k events per day with the standard CS Archive or a recent SQL Archive Server, this is sub-second. Above that scale, batch the filter update on operator commit (rather than per keystroke) by wrapping the action call in an event that fires when the last I/O field loses focus. If MSGNR ranges are typically narrow but time ranges are wide, build the MSGNR predicate once and cache it in a static-like WinCC text variable; only refresh the time predicate each commit.

Persist the last-used filter in the user archive (WinCC User Archive cycle) so the operator does not need to re-enter the bounds after a client restart. Cycle the user archive on the picture change event of the AlarmControl screen.

For multi-server / distributed WinCC (WinCC/Redundancy or WebNavigator / WinCC/WebUX clients), the MsgFilterSQL property is honoured on the local AlarmControl only; the underlying query fetches from the central archive. The MSGNR semantics on the local client may differ from the configured number in the engineering tool — always verify by opening the "Alarm Logging" > "Message Configuration" table and reading the runtime column header.

12. Frequently Asked Questions

Why does my C action compile but call SetPropChar with an empty SQL string?

The buffer SQL[120] is too small for the combined predicate; sprintf truncates silently and the trailing null lands mid-WHERE-clause, which the AlarmControl then rejects as empty. Increase the buffer to char SQL[260] or larger and instrument with printf("\r\n[FILTER] %s", SQL) to confirm the string is intact before the setter runs.

What datetime format does the AlarmControl expect in MsgFilterSQL?

Use single-quoted ISO-8601 style with exactly nine fractional seconds: 'YYYY-MM-DD HH:MM:00.000000000'. The nine nanosecond digits match the SQL Archive Server's datetime2 mapping; shorter formats return zero rows because SQL Server cannot coerce them to the column type.

Can I combine more than two predicates, for example adding STATE or PRIORITY?

Yes. Append additional parenthesised groups separated by AND/OR. A four-group example is: (DATETIME >= '...' AND DATETIME <= '...') AND (MSGNR >= 1 AND MSGNR <= 10) AND (PRIORITY >= 2) AND (STATE = 1). Each AND/OR operator must occur outside parentheses to bind pairs of predicates.

Is the VBScript version preferred over the ANSI-C version?

For WinCC V7.0+ projects targeting long-term maintenance, yes — VB scripts are easier to read, have automatic string handling, and integrate with WinCC Tag Diagnostics (APDiag) without manual printf plumbing. The ANSI-C version is still fully supported and remains in use in older V6 projects; the MsgFilterSQL contract is identical.

Does setting MsgFilterSQL affect the Archive Database Server or only the local view?

Only the local AlarmControl's view is filtered. The property is a client-side WHERE clause handed to the archive query; it does not alter the data stored in the SQL Archive Server or CS Archive. Multiple clients connecting to the same server each maintain their own filter string.

Back to blog