Creating an HTML Alarm Page for the S7-1200 / S7-1500 Web Server
The integrated web server of the SIMATIC S7-1200 (firmware V4.0 and later) and S7-1500 (firmware V1.0 and later) provides a mechanism for serving user-defined HTML pages directly from the CPU. This article explains how to design a custom alarm page similar to the alarm view of a SIMATIC HMI Panel — showing active and historical alarms with date/time stamps — and how to wire the data block tags that drive it.
1. Web Server Capabilities and Limits
The web server of an S7-1200 / S7-1500 CPU exposes two classes of pages:
- Standard pages automatically generated by the firmware: Identification, Diagnostic buffer, Module status, Communication, Variable status, and Data logs.
- User-defined pages authored by the engineer in HTML/CSS/JavaScript and loaded into the CPU with the project.
User-defined pages can read and write PLC tags using the AWP syntax, and they can be password-protected (admin / read-only / no access). They cannot trigger program execution directly — for write access the tag is updated and the cyclic OB1 picks it up. Page sizes are limited by load memory; the S7-1200 supports up to 30 fragments and the S7-1500 supports hundreds of fragments depending on the CPU's memory card size. Refer to the S7-1200 Programmable Controller System Manual and the S7-1500 Automation System System Manual for the exact limits of the target CPU.
2. Prerequisites
- Hardware: S7-1200 (CPU 1211C / 1212C / 1214C / 1215C / 1217C, firmware V4.2 or later recommended) or S7-1500 (CPU 1511 / 1513 / 1515 / 1516 / 1517 / 1518, any current firmware).
- Software: TIA Portal V15.1 or later. The S7-1500 web API was significantly expanded in V16; alarm-friendly JSON endpoints are available from firmware V2.6 on the S7-1500 and from V4.5 on the S7-1200.
- Firmware activation: Web server enabled in Device Configuration → Web server → Activate. The CPU's IP address and HTTP/HTTPS port are configured in the same dialog.
- User rights: Define at least one user with Read access for the alarm page, and a separate Administrator if write operations are required.
- HTML knowledge: Familiarity with HTML5, basic CSS, and JavaScript fetch / XMLHttpRequest.
3. Design the Alarm Data Block
Create a global DB (for example DB_Alarm) that holds the bit-level alarm flags, the timestamp of the last state change, and a small buffer for historical events. The data type DTL (Date_And_Time_Long, 12 bytes) is the standard way to capture timestamps in S7-1200 / S7-1500.
| Tag | Type | Comment |
|---|---|---|
ALARM_WORD |
WORD | Bit 0 = Emergency Stop, Bit 1 = Low Air, Bit 2 = Door Open, ... |
AlarmTime[0..15] |
Array of DTL | Last 16 alarm-change timestamps, FIFO |
AlarmCode[0..15] |
Array of BYTE | Alarm class for each FIFO entry (1 = active, 2 = acknowledged, 3 = cleared) |
AlarmIndex[0..15] |
Array of USINT | Bit index (0 = Emergency Stop, 1 = Low Air, ...) |
FIFO_Head |
USINT | Write pointer (0..15) |
FIFO_Count |
USINT | Number of valid entries (0..16) |
The cyclic OB1 detects rising / falling edges of each alarm bit and pushes a record into the ring buffer. A small edge-detection pattern is shown below.
// Edge detection in SCL
FOR #i := 0 TO 15 DO
#currentBit := "DB_Alarm".ALARM_WORD.%X#i;
#lastBit := "DB_Edge".LAST_WORD.%X#i;
IF #currentBit <> #lastBit THEN
"DB_Alarm".AlarmTime["DB_Alarm".FIFO_Head] := DTL#1970-01-01-00:00:00;
"DB_Alarm".AlarmTime["DB_Alarm".FIFO_Head] := READ_DTL(REQ := TRUE);
"DB_Alarm".AlarmCode["DB_Alarm".FIFO_Head] := #currentBit ? 1 : 3;
"DB_Alarm".AlarmIndex["DB_Alarm".FIFO_Head] := #i;
"DB_Alarm".FIFO_Head := ("DB_Alarm".FIFO_Head + 1) MOD 16;
IF "DB_Alarm".FIFO_Count < 16 THEN
"DB_Alarm".FIFO_Count := "DB_Alarm".FIFO_Count + 1;
END_IF;
"DB_Edge".LAST_WORD.%X#i := #currentBit;
END_IF;
END_FOR;
4. Build the HTML Alarm Page
User-defined pages live in the project tree under CPU → Web server → User-defined pages. The default file name is index.htm and it is the entry point loaded by the browser when accessing the CPU's IP address with no file in the URL.
4.1 AWP Command Syntax
The AWP (Automation Web Programming) syntax is the bridge between HTML and the PLC tag database. Only a small subset of commands is required for an alarm page:
| AWP construct | Function | Example |
|---|---|---|
<!-- AWP_In_Variable Name='.../...' --> |
Declares a tag read from the CPU | <!-- AWP_In_Variable Name='"DB_Alarm".ALARM_WORD' --> |
<!-- AWP_Out_Variable Name='...' --> |
Declares a tag written to the CPU | <!-- AWP_Out_Variable Name='"DB_Alarm".AckCommand' --> |
:=<tag>: |
Substitutes a tag value at page request | <td>:="DB_Alarm".AlarmTime[i]:</td> |
:=<tag>:= |
Form / URL write access | <input name='"DB_Alarm".AckCommand' value='1'> |
For array elements use the same index expression that the SCL code uses. The AWP parser understands MOD, +, -, and direct array subscripts.
4.2 Page Skeleton
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Live Alarm View</title>
<style>
body { font-family: Arial, sans-serif; background:#1e1e1e; color:#e6e6e6; }
table { width:100%; border-collapse:collapse; }
th, td { padding:6px 10px; border-bottom:1px solid #333; }
th { background:#2d2d2d; text-align:left; }
.ack { color:#ff9b00; }
.clr { color:#5d5d5d; text-decoration:line-through; }
.act { color:#ff4040; font-weight:bold; }
</style>
</head>
<body>
<h1>Active Alarms</h1>
<table id="alarmTable">
<thead><tr><th>#</th><th>Time</th><th>Alarm</th><th>State</th></tr></thead>
<tbody><!-- rows populated on load and by polling --></tbody>
</table>
<!-- AWP declarations -->
<!-- AWP_In_Variable Name='"DB_Alarm".FIFO_Count' -->
<!-- AWP_In_Variable Name='"DB_Alarm".AlarmTime[0..15]' -->
<!-- AWP_In_Variable Name='"DB_Alarm".AlarmCode[0..15]' -->
<!-- AWP_In_Variable Name='"DB_Alarm".AlarmIndex[0..15]' -->
<script>
const NAMES = ["Emergency Stop","Low Air","Door Open","Tank High",
"Tank Low","Pump Trip","Over Pressure","Sensor Fail"];
const tableBody = document.querySelector("#alarmTable tbody");
async function refresh() {
const r = await fetch("alarms.json");
const j = await r.json();
tableBody.innerHTML = "";
for (let i = 0; i < j.entries.length; i++) {
const e = j.entries[i];
const st = e.state === 1 ? "act" : (e.state === 2 ? "ack" : "clr");
const row = `<tr><td>${i+1}</td>
<td>${e.time}</td>
<td>${NAMES[e.alarmIndex] || ("Alarm "+e.alarmIndex)}</td>
<td class="${st}">${["CLEARED","ACTIVE","ACK"][e.state]}</td>
</tr>`;
tableBody.insertAdjacentHTML("beforeend", row);
}
}
setInterval(refresh, 1000);
refresh();
</script>
</body>
</html>
5. Step-by-Step: Commissioning the Alarm Page
-
Create the data block. In TIA Portal add a new global DB named
DB_Alarmwith the structure defined in section 3. Disable optimized block access for S7-1200 firmware before V4.4; for S7-1500 optimized access is fine and is the recommended setting. - Write the alarm logic. Add an FB or use the SCL snippet in section 3 inside OB1 to maintain the FIFO buffer.
- Enable the web server. Open the CPU's device configuration, expand Web server, tick Activate web server on this module, choose HTTP (port 80) or HTTPS (port 443), and create at least one user with read access.
-
Add user-defined pages. Right-click the CPU in the project tree → Web server → User-defined pages → Add new user-defined page. The entry file should be
index.htm; addalarms.jsonas a second file (see step 6). -
Reference tags in HTML. Use
<!-- AWP_In_Variable -->declarations to expose the FIFO tags, and the:=tag:syntax to read values. -
Author the JSON endpoint. Create a second file
alarms.jsonin the same folder. The CPU's web server returns it with content-typeapplication/jsonwhen the AWP parser processes it. This file is fetched by the JavaScript poll loop above. - Compile and download. Compile the project and download the hardware and software to the CPU. The HTML files are part of the load memory image, so a re-download is required whenever the files change.
-
Browse to the page. Open a browser and navigate to
http://<cpu-ip>/index.htmor simplyhttp://<cpu-ip>. The user-defined entry page should load and start pollingalarms.jsonevery second.
6. The JSON Endpoint alarms.json
The web server processes an AWP-enabled file on each request. The alarms.json file is therefore a template in which the AWP parser substitutes the values. A minimal version that returns the FIFO contents in chronological order:
{
"count": :="DB_Alarm".FIFO_Count:,
"entries": [
:#i=0; #i<16; ++#i:
{ "time": :="DB_Alarm".AlarmTime[#i]:,
"alarmIndex": :="DB_Alarm".AlarmIndex[#i]:,
"state": :="DB_Alarm".AlarmCode[#i]: },
:}
]
}
The AWP loop construct :#i=0; #i<16; ++#i: iterates the substitution. Note that empty FIFO slots return zeros and will appear in the table; the JavaScript filter e.time > "1970-01-01-00:00:00" can be added to drop them.
7. Verification
After commissioning, run the following checks:
-
Page load test. From a browser on the engineering station, browse to
http://<cpu-ip>. The page should load within one second. If it returns HTTP 404, the file is not part of the load memory image — re-download the project. -
Live update test. Force a bit in
ALARM_WORDwith the watch table. The new row should appear in the browser within the next poll cycle (≤1 s) with the correct timestamp and name. - Edge clear test. Clear the bit. The same row should change state from ACTIVE to CLEARED with the new timestamp recorded.
- Long-run test. Leave the page open for 24 h and verify the FIFO does not over-write valid entries. If the ring is shorter than 16 entries, extend the array in the data block and re-compile.
- Security test. Log out and re-open the URL. The CPU should prompt for credentials when Access protection is enabled.
8. Troubleshooting Matrix
| Symptom | Likely cause | Remediation |
|---|---|---|
| Browser shows "Page not found" | HTML files not loaded into the CPU | Re-download the project; check that the file is listed in User-defined pages |
| Page loads but values are empty | Missing AWP_In_Variable declaration or block is optimized and unsupported |
Add declarations; for S7-1200 < V4.4 disable optimized access for the DB |
Substitution returns # characters |
Tag name spelled wrong or contains characters not supported by AWP | Use single quotes around the path; check for the leading colon pair :=
|
| Browser shows login loop | No user defined for web access | Create a user with read permission in the web server configuration |
| Alarms update only on F5 | JavaScript polling not started | Check the browser console for errors; ensure setInterval is at the end of the script |
| Timestamp shows 1970-01-01 | FIFO slot not yet written | Filter out empty slots in the JSON rendering logic |
| S7-1200 stops responding after a few hours | Too many concurrent HTTP connections | Reduce poll frequency or move to HTTPS polling on a single client |
9. Extending the Page
Once the basic alarm view is working, several features can be added without changing the PLC code:
-
Acknowledge button: write a
1to a tag likeDB_Alarm.AckRequestusing a form POST; the PLC returns the current state toAckResponseand the browser updates the row. -
Filtering: add an
<input type="search">that uses client-side JavaScript to hide rows whose alarm index is not in the filter list. - Color theming: replace the inline CSS with a media query that switches to a light theme on demand.
- CSV export: build a Download link that constructs a CSV from the same JSON payload.
For a deeper reference, the Siemens application example Creating and using user-defined web pages on S7-1200 / S7-1500 (entry ID 58862931) contains ready-to-use tank-level and traffic-light examples that can be adapted to alarms. It is available from the Siemens Industry Online Support portal at https://support.industry.siemens.com/cs/ww/en/view/58862931.
10. Field-Engineering Notes
- Browser support: the page uses only standard HTML5 and ES6 fetch; tested in Chrome 120+, Firefox 120+, Edge 120+ and Safari 17+.
- HTTP vs HTTPS: enable HTTPS for any production line. The CPU generates a self-signed certificate; browsers will warn the first time, which is acceptable on a closed network.
- Cross-vendor use: the AWP syntax is Siemens-specific. On third-party controllers (Allen-Bradley, Beckhoff, etc.) the equivalent is the REST API of the controller (CIP for EtherNet/IP, TwinCAT ADS over TCP).
- Memory cost: each user-defined page consumes load memory roughly equal to the file size. The S7-1214C has 4 MB; the S7-1500 CPU 1511 has 30 MB. A typical alarm page is 4–8 KB, so dozens of pages can be hosted on a single CPU.
- Determinism: web server activity runs in a low-priority background task; alarm-flag evaluation in OB1 remains cycle-deterministic at the configured cycle time.
11. Reference Summary
| Item | Value |
|---|---|
| Minimum firmware (S7-1200) | V4.0 (basic); V4.5 recommended for full AWP coverage |
| Minimum firmware (S7-1500) | V1.0; V2.6+ recommended for JSON-friendly AWP |
| Default HTTP port | 80 (configurable) |
| Default HTTPS port | 443 (configurable) |
| Concurrent connections (S7-1200) | Up to 30 |
| Concurrent connections (S7-1500) | Up to 128 |
| Recommended poll interval | 1000 ms (adjustable 250–5000 ms) |
| FIFO length (default) | 16 records, expandable |
| AWP entry file | index.htm |
| Application example | Siemens entry ID 58862931 |
How do I expose a PLC tag to a user-defined web page?
Add a <!-- AWP_In_Variable Name='"DB_name".Tag' --> declaration in the HTML file. For writes use <!-- AWP_Out_Variable ... -->. The page must be part of the project tree under CPU → Web server → User-defined pages, and the project must be re-downloaded to push new files into the CPU.
Which firmware version of the S7-1200 is required for user-defined pages?
Web server is available from firmware V4.0 on every S7-1200 CPU. Full AWP tag support (arrays, MOD, conditional substitution) was tightened in V4.4 / V4.5. TIA Portal V15.1 or later is required to author the pages.
Can the page show a history of past alarms, not just current ones?
Yes. Maintain a ring buffer (FIFO) in the PLC data block — for example 16 entries of DTL, BYTE state, and USINT alarm index. Expose the buffer with AWP_In_Variable Name='"DB_Alarm".AlarmTime[0..15]' and an AWP loop, then format the result in the JSON endpoint and the JavaScript table.
How can I acknowledge an alarm from the browser?
Add a form <input name='"DB_Alarm".AckRequest' value='1'> and submit it to the same page or to "DB_Alarm".AckRequest via POST. The PLC evaluates the request in OB1, updates the AlarmCode entry to 2 (acknowledged), and the next poll shows the new state.
Why does the page show 1970-01-01 timestamps?
Empty FIFO slots are zero-initialised. Add a JavaScript filter such as if (!e.time || e.time.startsWith('1970-01-01')) continue; to drop them. Alternatively, the SCL edge-detection block can initialise the slot to DTL#1970-01-01-00:00:00 explicitly so the filter logic stays the same.
What is the maximum number of user-defined pages the CPU can host?
It is limited by load memory, not by a hard count. The S7-1200 supports up to 30 HTML fragments and the S7-1500 supports hundreds; each fragment is the size of the source file. For a typical alarm page (≈5 KB) this means a few hundred kilobytes of load memory, well within the capacity of any S7-1200 / S7-1500.