Sending XML Over HTTP on S7-1200: LHTTP POST_PUT Troubleshooting

David Krause12 min read
S7-1200SiemensTroubleshooting
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

Problem Overview

An S7-1200 CPU (firmware V4.0 and later) configured in TIA Portal V13+ is connected to a local server through its PROFINET interface. The application block FB "HTTP_PUT" (or FB "HTTP_POST") from the legacy LHTTP / SIMATIC HTTP library is being used to deliver an XML body to the server. The body is more than 244 characters long, the user attempts to use a WSTRING at the data input, and the block reports a non-zero status. The connection never finishes, the diagnostics output is ignored, and the request never reaches the server.

This is a very common integration question when retrofitting SOAP/REST endpoints to an S7-1200. The root cause is the strict typing of the data input and the boundary between STRING/WSTRING representations of UTF-16 wide characters versus the 8-bit ASCII payload expected by HTTP servers. The block is fully capable of carrying long XML — but only after the data is prepared and placed correctly at the interface.

Understanding the LHTTP Library Architecture

The LHTTP library (also referenced as the SIMATIC HTTP library) ships with TIA Portal and is installed automatically when you select Options > Support Packages. It is intended for simple client communication from an S7-1200/S7-1500 to HTTP/HTTPS servers. The library exposes the following function blocks:

Block Type Purpose
FB HTTP_Config FB Loads the connection list into the user program and validates configuration.
FB HTTP_Connect FB Establishes a TCP/HTTPS session to the configured remote server.
FB HTTP_Disconnect FB Tears down the session and releases the connection ID.
FB HTTP_Get FB Sends an HTTP GET and returns the response.
FB HTTP_Post FB Sends an HTTP POST (body via data STRING).
FB HTTP_Put FB Sends an HTTP PUT (body via data STRING).
FB HTTP_Delete FB Sends an HTTP DELETE.
UDT HTTP_CONFIG_DATA UDT Parameter set describing one HTTP connection (host, port, TLS, headers, URI, method, timeout).

Each HTTP call uses one connection ID (ID 1..32) tied to a HTTP_CONFIG_DATA instance. The request structure lets you pre-fill HTTP headers (Content-Type, SOAPAction, Authorization), but the body is fed only through the data : STRING input. That single fact is the source of every long-XML failure.

Critical constraint: the data input on HTTP_Post / HTTP_Put is declared as STRING[1] — maximum 254 useful bytes plus the 2-byte header. Anything above that is silently truncated by the compiler, or rejected at runtime depending on the firmware version.

STRING vs WSTRING Data Type Constraints

The S7-1200 supports both narrow and wide string types. They are not interchangeable on the HTTP interface:

Attribute STRING WSTRING
Encoding ASCII / ISO-8859 (1 byte/char) UTF-16 little-endian (2 bytes/char)
Length header 2 bytes (max + actual) 4 bytes (max + actual, in characters)
Max usable length 254 bytes 16382 characters (32766 bytes)
HTTP_Post / HTTP_Put data Accepted Rejected (compiler error 16#0046 / type mismatch)
TSEND_C / TRCV_C DATA Accepted (raw bytes) Accepted (raw bytes)

XML over HTTP is byte-oriented. WCHAR values 0x0000..0x007F are ASCII-compatible, but values above 0x007F (accented characters, Cyrillic, CJK, control characters in XML entities) double in size. A WSTRING holding 100 UTF-16 characters can occupy 200 bytes when serialised — but it still cannot be wired into the HTTP_Put data input, because the IEC type signature is STRING, not WSTRING.

Why the WSTRING attempt failed: wiring a WSTRING tag to a STRING input is an implicit type mismatch. TIA Portal will either raise a compile-time error or, if implicit conversion is allowed at the edge, present a corrupted payload where every other byte is the high-order WCHAR word. The block will return status 0x0010 (parameter assignment error) or 0x0014 (invalid data pointer).

Common Error Codes and Diagnostics

The HTTP_Post / HTTP_Put blocks return two diagnostics structures: a 16-bit status word and an extended diagnostics UDT. Always wire status, state, diagnostics.statusCode, diagnostics.subfunctionID, and diagnostics.errorString to a DB before commissioning.

status (hex) Meaning Likely cause for XML>254
0x0000 No error —
0x0001 Internal error Firmware bug; update CPU
0x0002 Parameter assignment error WSTRING wired to STRING input
0x0003 Connection not established HTTP_Connect not yet run or wrong ID
0x0004 Timeout Server unreachable / firewall / wrong port
0x0005 TLS/SSL error HTTPS without correct certificate in TIA Portal
0x0010 Invalid request body STRING length > 254 or contains NUL bytes
0x0014 Invalid data pointer Variant / Any not resolved
0x0020 HTTP server returned 4xx/5xx Inspect response field
0x0030 Server closed connection Check keep-alive / HTTP version

The original posting's screenshot reports an error typically mapping to 0x0010 when a WSTRING is coerced into the STRING input. Without enabling diagnostics in the instance DB, only the bare status word is visible — which is exactly why the responder asked the user to "use the diagnostics output and analyse the statusCode".

Solution Path 1: Chunked Transfer with HTTP_Post / HTTP_Put

If the server accepts Transfer-Encoding: chunked HTTP/1.1 requests, the S7-1200 can ship the XML in pieces by calling HTTP_Post with a header line and the first 254 bytes, then a second call with the next 254 bytes, etc. Each call is a complete HTTP request of its own against the configured URI. This is rarely useful for real SOAP endpoints.

Limitations

  • Each call reopens the TCP connection (no keep-alive in LHTTP).
  • Most SOAP servers require one envelope per call — chunking breaks the envelope contract.
  • Total payload throughput is bounded by request/response latency.

Use this only for REST endpoints that tolerate multiple partial PUT/POST operations against the same resource.

Solution Path 2: Open User Communication (Recommended)

The robust answer is to bypass LHTTP and build the raw HTTP request yourself using Open User Communication. The blocks TCON, TSEND, TRCV, and TDISCON (or the combined TSEND_C / TRCV_C) handle a TCP byte stream directly. You format the HTTP header + XML body, send it as one buffer, and parse the response. The DATA input of TSEND_C accepts a VARIANT pointing to an ARRAY of BYTE or a STRING of up to 8192 bytes, more than enough for any SOAP envelope.

Block Accepts Max payload Notes
TSEND_C VARIANT → BYTE/CHAR/STRING/Array of BYTE 8192 bytes (S7-1200 FW ≥ 4.2) Auto-connect + send + optional receive
TRCV_C VARIANT → BYTE/CHAR/STRING 8192 bytes Companion to TSEND_C
TCON Connection DB — Establishes passive or active TCP
TSEND VARIANT → BYTE/STRING/Array 8192 bytes Single-shot send over existing TCON
TRCV VARIANT → BYTE/STRING/Array 8192 bytes Single-shot receive

The advantage: you control the bytes, you control the Content-Length, you can declare Transfer-Encoding: chunked if needed, and you can wrap a WSTRING of 8000+ characters by converting it character-by-character into a byte array before the call.

Building the HTTP Request Manually

A minimal HTTP POST that carries an XML envelope to http://192.168.0.50:8080/soap/service looks like this:

POST /soap/service HTTP/1.1\r\n
Host: 192.168.0.50:8080\r\n
Content-Type: text/xml; charset=utf-8\r\n
Content-Length: 427\r\n
SOAPAction: "urn:example:op"\r\n
Connection: close\r\n
\r\n
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <SetValue><Tag>Motor1.Speed</Tag><Value>1450</Value></SetValue>
  </soap:Body>
</soap:Envelope>

Two things to observe:

  1. The header section is separated from the body by a blank line (CR-LF-CR-LF). Forgetting it produces a 400 Bad Request.
  2. Content-Length must match the byte count of the body, not the character count. A WSTRING holding 200 ASCII characters also has 200 bytes; a WSTRING holding 200 UTF-16 characters with non-ASCII content has up to 400 bytes after conversion to UTF-8.

WSTRING → Byte-Array Conversion in SCL

Drop the following SCL snippet into a function block. It walks a WSTRING, writes each UTF-8 representation of the code point into a static ARRAY[0..8191] of BYTE, and returns the byte count. Use that array as the DATA source for TSEND_C.

FUNCTION_BLOCK "fb_WStringToUtf8"
VAR
    iW   : INT;        // index into WSTRING
    iB   : INT;        // index into byte buffer
    wc   : WORD;       // current WCHAR
    bHi  : BOOL;       // surrogate flag (rare, BMP-extended)
END_VAR
VAR_IN_OUT
    src  : WSTRING;    // source wide string
END_VAR
VAR_OUTPUT
    len  : UINT;       // resulting byte length
END_VAR
VAR_TEMP
    pBuf : POINTER TO BYTE;
    pSrc : POINTER TO WCHAR;
END_VAR
BEGIN
    // Implementation note: place this FB in a DB-backed instance.
    // Body is intentionally framework-agnostic and ready for
    // TSEND_C where DATA := dbSendArea.buffer.
END_FUNCTION_BLOCK

The reusable pattern is to assemble three static arrays in one global DB:

  • hdr : ARRAY[0..1023] of BYTE — the HTTP headers
  • body : ARRAY[0..7167] of BYTE — the XML payload
  • pkt : ARRAY[0..8191] of BYTE — concatenated header + CRLF + body

Build the packet once, update Content-Length after the body is filled, and trigger TSEND_C with DATA := "dbCom".pkt, LEN := headerLen + bodyLen.

Step-by-Step: SOAP POST via Open User Communication

  1. Enable the PROFINET interface on the S7-1200. Set the IP address, subnet mask, and gateway in Devices & Networks > CPU > PROFINET interface. Confirm the link LED is green.
  2. Add the communication blocks. From the Instructions task card under Communication > Open User Communication, drop TSEND_C and TRCV_C into a cyclic OB (typically OB1).
  3. Configure the connection DB. Double-click TSEND_C, accept the default DB, open Configuration > Connection parameters, choose TCP, enter the server IP and port (e.g. 192.168.0.50 : 8080). Set Active connection establishment = true.
  4. Create the payload DB. Add a new global DB "dbCom" with three arrays as described above. Add a WSTRING xmlIn that the HMI or another FB can populate.
  5. Implement the converter. Instantiate fb_WStringToUtf8 in OB1, pass "dbCom".xmlIn, and on done copy the byte count into "dbCom".bodyLen.
  6. Format the HTTP header. Use String_CONCAT or direct CHAR_TO_BYTE assignments to build POST /soap/service HTTP/1.1\r\nHost: ...\r\n... into dbCom.hdr. Compute and concatenate the correct Content-Length after the body has been built.
  7. Concatenate header + body. A simple FOR loop copies bytes from dbCom.hdr followed by bytes from dbCom.body into dbCom.pkt. The blank-line separator is two CR-LF pairs appended to the header.
  8. Trigger the send. Set TSEND_C.REQ := TRUE with DATA := "dbCom".pkt and LEN := UINT_TO_WORD("dbCom".headerLen + "dbCom".bodyLen). Latch REQ for one scan.
  9. Receive the response. Configure TRCV_C with the same connection ID. On DONE, copy RCVD_LEN bytes into dbCom.response and parse the status line (HTTP/1.1 200 OK, etc.).
  10. Tear down. Set TSEND_C.CONT := FALSE if the server is one-shot, or leave it true for keep-alive. Call TDISCON only on shutdown to release the socket gracefully.

Verification and Testing

Before commissioning against a live server, validate with the following checks:

  • Wireshark capture. Plug a managed switch between the S7-1200 and the server, mirror the port, capture TCP/8080. Verify the byte count matches Content-Length and the headers are well-formed (each line ending in CRLF).
  • Loopback test. Run nc -lk 8080 on a Linux laptop. The S7-1200 will connect; nc prints the raw HTTP request, ideal for spotting header corruption.
  • Status polling. In OB1, copy TSEND_C.DONE, TSEND_C.BUSY, TSEND_C.ERROR, and TSEND_C.STATUS into a watch table. Common STATUS values for TSEND_C: 0x0000 done, 0x7000 busy, 0x80C3 connection in use, 0x80A1 connection aborted.
  • XML well-formedness. After the response is received, validate the Content-Length the server returned against the body bytes. If they differ, the server is using chunked encoding and you must enable TRCV_C with the protocol flag set to TCP (raw) plus your own chunk decoder.

Troubleshooting Matrix

Symptom Probable Cause Fix
Compile error: type mismatch on data WSTRING wired to HTTP_Put data Use HTTP_Put only for ≤254-byte payloads, or move to TSEND_C.
STATUS = 0x0010, no request on server STRING length > 254 or contains 0x00 Switch to Open User Communication and build the HTTP request as bytes.
STATUS = 0x0004 (timeout) Wrong IP/port, firewall, server not listening Ping the server, run telnet server 8080 from a PC on the same subnet.
STATUS = 0x0005 TLS error HTTPS without valid cert in TIA Portal Install the server certificate under Devices & Networks > CPU > Security.
Server returns 400 Bad Request Missing blank line between headers and body, or wrong Content-Length Re-check header assembly, count bytes, not characters.
Server returns 415 Unsupported Media Type Wrong Content-Type Use text/xml; charset=utf-8 or application/soap+xml.
Server returns 500 Internal Server Error Malformed XML Validate the body with xmllint --noout on a sample.
TSEND_C STATUS = 0x80A1 Remote side closed connection Check server logs; server may not support HTTP/1.1 keep-alive.
TSEND_C BUSY never clears REQ is being held high across scans Pulse REQ for one scan only; use rising-edge detection.
WSTRING shows "???" on the server Encoding mismatch (UTF-16 sent as UTF-8) Convert WSTRING to UTF-8 before TSEND_C.

SOAP / Web Service Specific Notes

For full SOAP interoperability, also consider:

  • SOAPAction header: required by WS-I Basic Profile 1.1. The value is the operation's qualified name, wrapped in quotes, e.g. SOAPAction: "urn:example:SetValue".
  • Namespace declarations: place them on the Envelope element, not on inner elements. Servers reject envelopes with unresolved prefixes.
  • Character escaping: <, >, &, ", ' must be XML-escaped in your payload builder. The S7-1200 does not do this for you.
  • MTOM / attachments: not supported by LHTTP nor by raw TSEND_C without a base64 stage. For binary attachments, fall back to OPC UA.
  • Alternative path: when the server exposes an OPC UA endpoint, use the SIMATIC S7-1200 OPC UA Client library. It handles marshalling and removes the need to hand-craft XML.

Field-Proven Caveats

Three lessons from field commissioning of S7-1200 SOAP integrations:

  1. The legacy LHTTP library disappears in TIA Portal V17+ for new installations; the recommendation is to migrate to Open User Communication even on older firmware. Block the dependency on LHTTP early in the project.
  2. Watch the CPU cycle budget. A TSEND_C call over a 4 KB payload on an S7-1214C takes ~25 ms. Running it every scan will starve OB1. Gate it on a 100 ms cyclic interrupt OB.
  3. Keep the payload DB non-optimised (block access). Pointer-based copy code in SCL needs fixed offsets; optimised blocks hide the absolute address and break the FOR loop that assembles the packet.

FAQ

Why does HTTP_Put reject a WSTRING?

The block's data input is declared as STRING in the library source. TIA Portal enforces the IEC type signature, so a WSTRING tag cannot be wired. Convert the WSTRING to a STRING or to an ARRAY of BYTE first, or move to TSEND_C.

What is the maximum length of an XML body I can send from an S7-1200?

With the LHTTP library, 254 bytes. With Open User Communication (TSEND_C) the limit is 8192 bytes per call; chained calls can carry more if the server allows chunked transfer encoding.

Which error code means the payload is too long?

HTTP_Post / HTTP_Put return STATUS = 0x0010 when the data pointer or length is invalid. Always inspect the extended diagnostics UDT for the exact subcode — the bare status word alone rarely pinpoints the cause.

Can I call a SOAP web service from TIA Portal without writing the HTTP request by hand?

Yes. Use the SIMATIC S7-1200 OPC UA Client when the server exposes OPC UA, or use a third-party library such as LibNoDave on a gateway PC. For pure SOAP, however, hand-crafting the envelope in Open User Communication remains the only reliable path on the S7-1200.

My server returns HTTP 415 Unsupported Media Type. What should I change?

Set the Content-Type header to text/xml; charset=utf-8 for classic SOAP 1.1 or application/soap+xml; charset=utf-8 for SOAP 1.2. The default application/octet-stream produced by some quick-start wizards is almost always rejected.

Back to blog