Creating S7-1200/S7-1500 HTML Alarm Page Using Web Server

David Krause11 min read
S7-1200SiemensTutorial / 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

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.

Engineering context. The user-defined web page capability uses the AWP (Automation Web Programming) command syntax inside standard HTML files. No additional runtime license is required beyond the standard web server activation in TIA Portal. The HTML, CSS, and JavaScript files are stored in the CPU's load memory and transferred with the project.

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

  1. 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).
  2. 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.
  3. 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.
  4. User rights: Define at least one user with Read access for the alarm page, and a separate Administrator if write operations are required.
  5. 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

  1. Create the data block. In TIA Portal add a new global DB named DB_Alarm with 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.
  2. Write the alarm logic. Add an FB or use the SCL snippet in section 3 inside OB1 to maintain the FIFO buffer.
  3. 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.
  4. 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; add alarms.json as a second file (see step 6).
  5. Reference tags in HTML. Use <!-- AWP_In_Variable --> declarations to expose the FIFO tags, and the :=tag: syntax to read values.
  6. Author the JSON endpoint. Create a second file alarms.json in the same folder. The CPU's web server returns it with content-type application/json when the AWP parser processes it. This file is fetched by the JavaScript poll loop above.
  7. 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.
  8. Browse to the page. Open a browser and navigate to http://<cpu-ip>/index.htm or simply http://<cpu-ip>. The user-defined entry page should load and start polling alarms.json every second.
Performance tip. Reduce the poll interval to 500 ms only if the page is viewed on a local network. Each request occupies one web server connection slot; S7-1200 firmware V4.5 supports up to 30 concurrent connections, and S7-1500 supports up to 128.

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:

  1. 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.
  2. Live update test. Force a bit in ALARM_WORD with the watch table. The new row should appear in the browser within the next poll cycle (≤1 s) with the correct timestamp and name.
  3. Edge clear test. Clear the bit. The same row should change state from ACTIVE to CLEARED with the new timestamp recorded.
  4. 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.
  5. 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 1 to a tag like DB_Alarm.AckRequest using a form POST; the PLC returns the current state to AckResponse and 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.

Back to blog