S7-1200 Web Server: Exposing Array Data as CSV String

David Krause12 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

S7-1200 Web Server: Exposing Array Data as CSV String for PHP Clients

The SIMATIC S7-1200 CPU ships with a built-in Web Server that is intended for diagnostics, status visualization, and lightweight custom HTML pages driven by PLC tag references. Many integrators quickly discover that the Web Server cannot iterate or render ARRAY tags directly inside an HTML fragment, which breaks the obvious approach of dropping :="MYDB".EXCHANGE_STRING: into a custom page when the underlying tag is ARRAY[0..1000] OF CHAR.

This reference documents a field-proven workaround: assemble the array contents into a single STRING tag (CSV-encoded) inside an SCL block, expose that one string on a custom Web Server page, and pull the page body from any PHP process using file_get_contents(). A Snap7 alternative for non-Web-Server deployments is also documented.

1. Problem Statement and Architectural Constraints

The S7-1200 Web Server (firmware V4.0 and later, with significant improvements through V4.5/V4.6) supports only elementary data types as direct tag substitutions inside an HTML page. The substitution syntax :="DB_Name".TagName: accepts:

  • BOOL, BYTE, WORD, DWORD, LWORD
  • SINT, INT, DINT, LINT and their unsigned variants
  • REAL, LREAL
  • CHAR, WCHAR
  • STRING, WSTRING

Arrays, structures (STRUCT), and user-defined data types (UDTs) cannot be dereferenced by the Web Server tag interpreter. The Web Server will return a parse error or a blank substitution when asked to render ARRAY[0..1000] OF CHAR, regardless of how the tag is named or qualified in the HTML. This is documented in the S7-1200 Web Server function manual (entry ID 109755216) under the section describing AWP (Automation Web Programming) commands.

Design implication: The PLC must pre-flatten any array of sensor readings, alarm messages, or status words into a single character buffer before the Web Server can hand it to a remote consumer. This moves serialization into the CPU, which is the only place where the array is addressable in the first place.

2. System Architecture and Topology

The deployment pattern is typically:

S7-317F CPU Automation PLC ~500–700 tags PROFINET S7-1200 CPU Data Exchange PLC Web Server active DB with CSV string Firmware V4.4+ Custom page: /data PC / Server PHP + MySQL file_get_contents() CSV → DB import PN/IE HTTP GET

The S7-317F (or any automation CPU) publishes its process image to the S7-1200 over PROFINET. The S7-1200 serializes the resulting data into a single STRING tag, exposes it on a custom HTML page, and the PC polls that page. The Web Server is not designed as a high-throughput webservice; it is sized for diagnostics traffic. For polling intervals of one to five seconds, it performs reliably on a CPU 1212C, 1214C, or 1215C.

3. Prerequisites

  • SIMATIC S7-1200 CPU with firmware V4.0 or higher (V4.4 or V4.6 recommended for the most stable AWP behavior and TLS support).
  • Siemens TIA Portal V15.1 or higher matching the CPU firmware (TIA V17 with FW 4.5, TIA V18 with FW 4.6, etc.).
  • CPU with sufficient work memory — a 1000-byte CSV buffer with surrounding logic fits comfortably in any 1212C and above.
  • Ethernet interface configured, HTTP/HTTPS service enabled under Device configuration → Web Server.
  • A user with read privileges for the target DB (configured under Web Server → User management).
  • PC running PHP 7.4+ with allow_url_fopen = On in php.ini (default).
  • Optional: Snap7 (snap7.sourceforge.net) if the PHP path is replaced by a native C/C++/Python client.

4. Building the CSV String on the S7-1200

4.1 Data Block Layout

Create a global DB (e.g., DB_DataExchange) containing a header DB and the CSV buffer:

Symbol Type Initial value Comment
HEADER_CSV String[64] 'TIMESTAMP;TAG;VALUE\r\n' CSV header row
EXCHANGE_STRING String[2000] '' Body exposed to Web Server
UPDATE_REQ Bool false Trigger FB to rebuild string
LAST_BUILD_MS DInt 0 Cycle stamp from RD_SYS_T
BUILD_COUNT DWord 0 Diagnostics counter
Sizing rule: The S7-1200 STRING type stores up to 254 bytes by default. To exceed 254 bytes, declare STRING[2000]. For buffers larger than 2048 bytes, segment the output across multiple custom pages to keep the Web Server response under the 4 KB request ceiling of small CPUs.

4.2 SCL Block: Build CSV

The following SCL FB (callable from OB1 or a cyclic OB) assembles CSV rows from internal tags and a PROFINET-transferred input area. The block uses CONCAT, DELETE, INSERT, and STRING_TO/TO_STRING from the standard IEC library.

FUNCTION_BLOCK "FB_BuildCsvString"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT
      i_Trigger   : Bool;     // rising edge rebuilds the string
      i_Timestamp : DTL;      // from RD_SYS_T
      i_TagName   : String[32];
      i_Value     : Real;
   END_VAR
   VAR_OUTPUT
      o_Built     : Bool;
      o_BytesUsed : DInt;
   END_VAR
   VAR
      sLine       : String[200];
      sValue      : String[32];
      fbTof       : TOF;       // edge qualifier
      rTrigPrev   : Bool;
   END_VAR
BEGIN
   // Edge detection on i_Trigger
   IF i_Trigger AND NOT rTrigPrev THEN
      // Convert timestamp DTL to ISO-like string
      sLine := '';
      sLine := CONCAT(IN1 := STRING_TO_INT(IN := INT_TO_STRING(IN := i_Timestamp.YEAR)),
                      IN2 := '-');
      // ... append month/day/hour/minute/second (omitted for brevity)

      // Convert REAL value with fixed 3 decimals
      sValue := '';
      // Use the SCL built-in conversion for predictable formatting
      sValue := Real_To_String(i_Value, 3);   // platform helper or manual FORMAT

      // Build the row: TIMESTAMP;TAG;VALUE\r\n
      sLine := CONCAT(IN1 := sLine, IN2 := ';');
      sLine := CONCAT(IN1 := sLine, IN2 := i_TagName);
      sLine := CONCAT(IN1 := sLine, IN2 := ';');
      sLine := CONCAT(IN1 := sLine, IN2 := sValue);
      sLine := CONCAT(IN1 := sLine, IN2 := '$r$n');

      // Append to the buffer
      IF (LEN("DB_DataExchange".EXCHANGE_STRING) + LEN(sLine)) < 2000 THEN
          "DB_DataExchange".EXCHANGE_STRING :=
              CONCAT(IN1 := "DB_DataExchange".EXCHANGE_STRING,
                     IN2 := sLine);
      END_IF;

      "DB_DataExchange".BUILD_COUNT := "DB_DataExchange".BUILD_COUNT + 1;
      o_Built := TRUE;
   ELSE
      o_Built := FALSE;
   END_IF;

   rTrigPrev := i_Trigger;
   o_BytesUsed := LEN("DB_DataExchange".EXCHANGE_STRING);
END_FUNCTION_BLOCK

4.3 Reset and Watchdog

Because the buffer is finite, clear it periodically or on a dedicated RESET_REQ bit. A simple watchdog in OB1 triggers a rebuild every N milliseconds using RD_SYS_T deltas:

// In OB1, call once per cycle
IF (ton_Build.Q = FALSE) THEN
   ton_Build(IN := TRUE, PT := T#2S);  // rebuild every 2 s
END_IF;
IF ton_Build.Q THEN
   "DB_DataExchange".UPDATE_REQ := TRUE;
END_IF;

5. Configuring the Custom Web Page in TIA Portal

5.1 Create the HTML Fragment

Under the CPU device configuration open Web Server → User-defined pages, add a new page named data. TIA Portal generates a default .htm entry with the AWP syntax already wired. Replace the body with:

<!-- :AWP_Use_Fragment="data,fragment1" -->
<h1>DATA:</h1>
<pre>
:="DB_DataExchange".EXCHANGE_STRING:
</pre>

The AWP fragment directive tells the Web Server compiler that the contents of the file should be embedded inside a default chrome (header, navigation) at request time. The substitution line is processed by the Web Server on every HTTP request, so the latest string is always returned.

5.2 Tag Read Permission

Open Web Server → User-defined pages → Tag access and add DB_DataExchange.EXCHANGE_STRING with read-only permission for the PHP user. Without this entry the substitution returns an empty string, even when the DB is correctly populated.

5.3 Compile and Download

After saving, TIA Portal compiles the HTML fragments and stores them in the CPU file system on download. Verify in Project tree → CPU → Web server → User-defined pages that data.htm is checked for generation.

6. Web Server Activation and Security

  1. Open the CPU Properties → Web Server and tick Activate web server on this module.
  2. Tick Permit access only with HTTPS for production deployments — self-signed certificates are acceptable when the PC trusts the CPU's CA.
  3. Under User management, add a user (e.g., phpclient) with the role Read for the data DB. Do not use the Administrator role for unattended scripts.
  4. Compile hardware and download to the CPU. The Web Server starts within 5–10 seconds of run-up.
  5. From the PC, test with:
    curl -k -u phpclient:<password> https://<plc_ip>/data
Security: Disable the Enable Ftp server option if not strictly required. The FTP server on the S7-1200 is documented in Siemens entry 109747254 and is not a substitute for the Web Server in real-time applications — the FTP path will buffer files to the SD card, introducing latency and wear.

7. PHP Client Implementation

The PHP side does not need a browser engine. The Web Server response is plain HTML/CSV text, so file_get_contents() with HTTP Basic authentication is sufficient:

<?php
declare(strict_types=1);

$plcIp   = '192.168.0.10';
$plcUser = 'phpclient';
$plcPass = 'ChangeMe!2024';
$url     = "https://$plcIp/awp/data.htm";

$ctx = stream_context_create([
    'http' => [
        'method'        => 'GET',
        'header'        => "Authorization: Basic " .
                              base64_encode("$plcUser:$plcPass") . "\r\n",
        'timeout'       => 5,
        'ignore_errors' => true,
    ],
    'ssl' => [
        'verify_peer'      => false,   // self-signed PLC cert
        'verify_peer_name' => false,
    ],
]);

$body = @file_get_contents($url, false, $ctx);
if ($body === false) {
    error_log("PLC unreachable at $url");
    exit(1);
}

// Strip HTML chrome, keep only the <pre> block
if (preg_match('/<pre>(.+?)<\/pre>/s', $body, $m)) {
    $csv = $m[1];
} else {
    error_log('No <pre> payload in PLC response');
    exit(2);
}

// Import to MySQL via LOAD DATA INFILE or batch INSERT
$rows = array_filter(explode("\r\n", trim($csv)));
$stmt = $pdo->prepare(
    'INSERT INTO plc_history (ts, tag, value) VALUES (?, ?, ?)'
);
foreach ($rows as $row) {
    [$ts, $tag, $value] = explode(';', $row);
    $stmt->execute([$ts, $tag, (float)$value]);
}

Schedule this script with cron, Windows Task Scheduler, or a systemd timer. Polling at 1–2 s is realistic for a 1214C; above 5 s the buffer should be cleared on the PLC side to prevent duplicate rows on each rebuild.

8. Alternative Path: Snap7 Direct Connection

If the PC cannot (or should not) rely on the Web Server, Snap7 provides a native S7 communication library that talks ISO-on-TCP (port 102) directly to the CPU. A PHP wrapper around the C library, or a small Python service that pushes to MySQL, removes the HTML/Web Server layer entirely and exposes the original DB tags — including arrays — to the application.

Aspect Web Server + CSV string Snap7 direct read
PLC code change FB needed to flatten arrays None (read DB bytes directly)
Network port 80 / 443 (HTTPS) 102 (ISO-TSAP)
Authentication PLC user mgmt + Basic Auth PLC protection level (none / password / write-protection)
Throughput Limited by HTTP overhead Native, hundreds of tags/s
Software dependency PHP only Snap7 + language binding
Real-time behavior Polling-driven Polling or event-driven

For multi-vendor or OPC UA environments, an OPC UA server can be added via the S7-1200 as of firmware 4.4 with the OPC UA server option activated, or via a Siemens SIMATIC IPC running the S7-1500 OPC UA server in proxy mode. This is documented in Siemens entry 109748955.

9. Performance and Sizing Considerations

The S7-1200 Web Server processes AWP substitutions sequentially. Each substitution involves a DB lookup, type conversion to ASCII, and write into the response buffer. A 1500-byte CSV string with a single substitution costs roughly 0.3–0.6 ms of OB1 time on a CPU 1214C at FW 4.4 — negligible against a 100 ms OB1 cycle, but a concern on a 1 ms cycle.

Best practices observed in field deployments:

  • One tag per page, three pages max: split large exports across data1.htm, data2.htm, data3.htm instead of one 4 KB page. The Web Server handles multiple short pages more predictably than one long one.
  • Trigger rebuilds, not polling: if the PC polls faster than the PLC rebuilds, the response is identical and the buffer is not updated. Rebuild on a 1–2 s timer driven by RD_SYS_T.
  • Avoid HTML in the buffer: characters like <, >, &, and " will be HTML-escaped by the Web Server. Keep the payload as plain CSV; encode on the PC side if the values can contain special characters.
  • Watch the SD card: extended user-defined pages are stored on the SD card. Do not generate the page dynamically on the PLC; use static fragments compiled by TIA Portal.
  • Use HTTPS: HTTP Basic authentication over plain HTTP exposes the password to network capture. Enable HTTPS even with a self-signed cert.

10. Verification and Troubleshooting

Symptom Likely cause Fix
Browser shows literal :="DB...".TAG: AWP fragment not declared; page is not in User-defined pages Add the page under Web Server → User-defined pages, recompile, redownload
Tag returns empty string User lacks read permission for the DB Add the tag under Tag access permissions for the user
HTTP 403 Forbidden Wrong username/password, or user disabled Verify in CPU properties → Web Server → User management
HTTP 404 on /data.htm Page name does not match the file name; case-sensitive on some firmwares Rename the page to match the request exactly
Characters are HTML-escaped (&lt;) Web Server escapes special chars by default Wrap with <:= :> raw output directive (FW 4.4+)
Connection times out from PC Firewall or wrong port; HTTPS-only enabled but PC tries HTTP Use https://; allow port 443 in the network policy
OB1 cycle spikes when page is requested Large buffer rebuild while serving Decouple: rebuild in a separate OB (e.g., OB35) and gate the Web Server read
Buffer overflows, old data persists No reset logic; consumer fell behind Implement a watchdog that clears EXCHANGE_STRING after the consumer ACKs

Verification Steps After Commissioning

  1. From a browser, log in to https://<plc_ip>/ and navigate to the custom page. Confirm the CSV header row appears.
  2. Force a tag value change on the S7-317F, wait 2 s, refresh the page — the new value should be visible in the <pre> block.
  3. From the PC, run the PHP script manually. Confirm rows are inserted into MySQL with the expected ts, tag, and value columns.
  4. Inspect DB_DataExchange.BUILD_COUNT and LAST_BUILD_MS in the Web Server diagnostics to confirm rebuilds are happening at the configured interval.
  5. Run for 24 h, then check the MySQL row count and confirm zero duplicate primary keys (indicates that the consumer is keeping up with the rebuild rate).

11. Frequently Asked Questions

Can the S7-1200 Web Server render an ARRAY tag directly in HTML?

No. The Web Server substitution syntax only accepts elementary data types (BOOL, INT, REAL, STRING, etc.). Arrays and STRUCTs must be flattened into a single STRING tag in an FB/FC before the Web Server can return them, as documented in Siemens entry 109755216.

What is the maximum STRING length the S7-1200 Web Server can return?

The STRING type in TIA Portal is sized explicitly, up to 254 bytes by default and up to 2048 bytes when declared as STRING[2000] (firmware V4.2+). For payloads above 2 KB, split across multiple user-defined pages to keep individual HTTP responses small and predictable.

Does the Web Server support HTTPS and Basic authentication?

Yes, from firmware V4.0 the S7-1200 supports HTTPS with self-signed certificates and per-user roles (Administrator, Read, Write). Production deployments should enable HTTPS and use a dedicated low-privilege user for the PHP client, never the Administrator account.

Is the S7-1200 FTP server a real-time substitute for the Web Server?

No. The FTP server writes files to the SD card and is intended for recipe transfer and bulk data export. It introduces SD-card wear and millisecond-to-second latency. For polled webservice-style access, the Web Server is the correct tool. For high-throughput or event-driven access, use Snap7 or OPC UA.

Can I poll faster than 1 s without overloading the CPU?

Polling at 200–500 ms works on a 1214C/1215C if the CSV buffer is short (under 500 bytes) and the rebuild is decoupled into a cyclic OB. Below 200 ms, prefer Snap7 or OPC UA; the Web Server HTTP/TLS stack becomes the bottleneck long before the PLC cycle time does.

How does this compare to using Snap7 from PHP directly?

Snap7 avoids the Web Server entirely and reads raw DB bytes over ISO-on-TCP (port 102). It removes the CSV-flattening step and exposes arrays natively, but requires a PHP extension (or a sidecar service) and the PC must reach port 102 — which is often blocked by IT firewalls. The Web Server path is simpler to deploy and firewall-friendly because it uses standard HTTP/HTTPS.

Back to blog