Configuring Node-RED TCP/IP with Siemens S7 Open User Communication (OUC)
Siemens S7-1200/1500 controllers expose Open User Communication (OUC) as a set of standard library function blocks: TCON, TDISCON, TSEND, TRCV, plus the combined TSEND_C and TRCV_C. These blocks run on ISO-on-TCP (RFC1006), TCP, or UDP and are designed to interoperate with any socket-capable peer. Node-RED's built-in tcp-in and tcp-out nodes, found in the standard node-red-node-tcp package, present a clean socket surface, so the two can be paired in a matter of minutes — provided the data format, byte order, and TCON parameters on the PLC side are correct.
This article gives a working configuration for Node-RED ↔ S7-1500 TCP traffic, explains how to format integer, boolean, real, and string payloads so that they decode cleanly on both ends, and walks through the most common field failure: persistent STATUS = 16#7006 on TRCV_C with no incoming data.
1. Overview of the OUC-to-Node-RED Path
Open User Communication runs on the S7's industrial Ethernet interface and is configured per connection through a TCON parameter block. Each connection requires a unique ID from 1 to 4095 (S7-1500 connection resource), a partner IP/port, and a connection mode. Node-RED, in turn, opens a TCP socket against the PLC's IP and the port declared on the OUC side.
| Direction | S7 Side Block | Node-RED Node | Trigger |
|---|---|---|---|
| PLC → Node-RED | TSEND_C (FB 1588) | tcp-in | REQ rising edge / job trigger |
| Node-RED → PLC | TRCV_C (FB 1589) | tcp-out | CONT = 1, data received on socket |
| Connection owner | TCON (FB 1500) / TSEND_C / TRCV_C | Socket open by node | Passive or active open |
Unlike the S7 communication model used by S7Comm/ISO-on-TCP servers such as Snap7, OUC uses raw TCP with no Siemens header. The PLC is therefore agnostic of Node-RED — anything that can write bytes to a socket is a valid peer.
2. Prerequisites
- Siemens S7-1200 (FW 4.0 or later) or S7-1500 with Ethernet interface.
- SIMATIC TIA Portal V15.1 or later installed; Open User Communication blocks are part of the standard library shipped with every TIA Portal install.
- Node-RED 2.x or 3.x running on a host reachable from the PLC subnet.
-
node-red-node-tcppackage installed:npm install node-red-node-tcp(or use the palette manager inside Node-RED). - Static IP addresses on both peers (DHCP reservations are acceptable for production but complicate commissioning).
- Port 2000..2010, 5000, or any unprivileged port above 1024 — the OUC blocks do not require privileged ports, but they must not collide with HMI, PN, or OPC UA on the same CPU.
3. PLC Hardware Configuration in TIA Portal
Open the device view of the S7 CPU and switch to Properties → General → PROFINET interface [X1] → Ethernet addresses. Configure:
-
IP address: e.g.
192.168.0.10 -
Subnet mask:
255.255.255.0 - Router (optional): gateway address if Node-RED is on another subnet
Under Connection resources, verify the available OUC slots. The S7-1511 supports up to 64 connection resources, the S7-1515 up to 128, and the S7-1518 up to 320. Each OUC instance consumes one. Set aside IDs 1..16 for OUC; reserve 200+ for HMI, 16x for S7 connections, and so on, to avoid collision with other system services.
4. TCON Parameter Block
Each OUC connection requires a TCON_Param data block (DB of type TCON_Param or a UDT with the structure shown below). The block can be created manually, by using the TCON_IP_v4 / TCON_IP_RFC system data types, or implicitly when a TSEND_C/TRCV_C instance is inserted (TIA generates the parameter DB for you).
| Field | Type | Value (example) | Notes |
|---|---|---|---|
| InterfaceId | HW_ANY | 64 (Word) | 64 = PN interface X1, 65 = X2 |
| ID | CONN_OUC | 1 | Unique connection ID, 1..4095 |
| ConnectionType | BYTE | 16#0B (TCP) or 16#0C (ISO-on-TCP) | Use 0x0B for raw TCP with Node-RED |
| ActiveEstablished | BOOL | TRUE | PLC is active (opens socket to Node-RED) |
| RemoteAddress | ARRAY[1..4] OF BYTE | [192,168,0,50] | Node-RED host IP |
| RemotePort | UINT | 1880 | TCP port Node-RED listens on |
| LocalPort | UINT | 2000 | PLC-side listen port (only used if active = FALSE) |
| LocalTsapId | ARRAY[1..16] OF BYTE | n/a for TCP | Only relevant for ISO-on-TCP (RFC1006) |
When the ActiveEstablished flag is TRUE, the PLC will open the socket to Node-RED. If FALSE, Node-RED must connect to the PLC; this is the more common pattern because it allows Node-RED to reconnect automatically after a flow deploy.
5. TSEND_C Implementation (PLC → Node-RED)
Drop a TSEND_C instance (FB 1588) into a cyclic OB (typically OB1). The block signature on S7-1500 is:
// SCL excerpt of TSEND_C call
"instTSEND_C"(REQ := bSendTrig,
CONT := TRUE, // keep connection open
DATA := "dbPayload".txBuf, // VARIANT pointing to source data
LEN := UINT#20, // bytes to send (max 8192)
DONE => bSendDone,
BUSY => bSendBusy,
ERROR => bSendError,
STATUS=> wSendStatus);
Set CONT = TRUE so the TCP connection stays established across multiple send jobs. REQ is a rising-edge trigger; on each pulse, exactly LEN bytes are copied from DATA into the send buffer.
5.1 Receive indicator
For monitoring, evaluate STATUS against the TCON/TSEND status codes (see section 10). A successful cycle moves through 16#7002 (job running) and settles on 16#0000 with DONE set for one cycle.
6. TRCV_C Implementation (Node-RED → PLC)
Mirror the setup with a TRCV_C instance (FB 1589):
// SCL excerpt of TRCV_C call
"instTRCV_C"(EN_R := TRUE, // permanently enabled
DATA := "dbPayload".rxBuf, // destination buffer
LEN := UINT#0, // 0 = accept whatever arrives
RCVD_LEN=> wBytesReceived,
BUSY => bRcvBusy,
ERROR => bRcvError,
STATUS => wRcvStatus);
Setting LEN = 0 tells the block to accept any length between 1 and the buffer size. The actual byte count of the last successful reception is returned in RCVD_LEN.
7. Node-RED TCP-Out Node (Sending to PLC)
The tcp-out node from node-red-node-tcp is the only standard node that opens a TCP socket from Node-RED. Configure it as follows:
- Drag a tcp out node onto the canvas.
-
Type:
msg.payload -
Host:
192.168.0.10(the PLC's IP) -
Port:
2000(matches the PLC's local port if ActiveEstablished=FALSE, or any port if the PLC is the active opener) - Return: leave unchecked unless you need an end-of-frame delimiter
- Newline: leave blank — TCP is a stream protocol, frame boundaries are your problem
For booleans, a function node can serialize a single byte:
msg.payload = Buffer.from([msg.payload ? 1 : 0]);
return msg;
8. Node-RED TCP-In Node (Receiving from PLC)
The tcp-in node listens for incoming connections. It will accept the connection opened by the PLC when ActiveEstablished=TRUE in TCON_Param.
- Drag a tcp in node.
-
Type:
Listen on port -
Port:
1880 -
Output:
stream of Buffer(best for binary payloads) orstream of String(best for ASCII) - Close connection: unchecked, so the socket stays open across flow deploys
To decode an INT16 (S7 INT is 16-bit, big-endian) arriving in msg.payload:
// Convert a 2-byte Buffer to a signed 16-bit integer (S7 INT)
const buf = Buffer.isBuffer(msg.payload) ? msg.payload : Buffer.from(msg.payload);
if (buf.length < 2) { node.warn("short frame"); return null; }
const value = buf.readInt16BE(0);
msg.payload = value;
return msg;
9. Data Type Formatting Reference
The most common complaint on the OUC ↔ Node-RED path is "INTs are displayed strangely in the PLC, Bools work". The root cause is almost always byte order. S7 stores integers in big-endian (MSB first); most microcontrollers and Node.js's writeInt16LE default to little-endian. Always serialize as big-endian.
| S7 Type | Bytes | Node-RED Encode | Node-RED Decode | S7 Buffer Slice |
|---|---|---|---|---|
| BOOL | 1 | Buffer.from([v ? 1 : 0]) |
buf[0] !== 0 |
AT in DB: Array[0..0] of BYTE then AT view to BOOL |
| INT | 2 | b.writeInt16BE(v, 0) |
b.readInt16BE(0) |
AT view Array[0..1] of BYTE → INT |
| DINT | 4 | b.writeInt32BE(v, 0) |
b.readInt32BE(0) |
AT view Array[0..3] of BYTE → DINT |
| REAL | 4 | b.writeFloatBE(v, 0) |
b.readFloatBE(0) |
AT view Array[0..3] of BYTE → REAL |
| STRING | 2+N | length-prefixed, see below | length-prefixed, see below | DB field of type STRING[n] |
9.1 Sending a STRING from Node-RED to the PLC
S7 STRING is laid out as [max_len, cur_len, char0, char1, ...]. The first byte holds the declared maximum length, the second the actual current length, and the payload follows. The PLC ignores the first byte on receive — the second byte is overwritten with the real length. To pack a string in Node-RED:
function packS7String(s) {
const maxLen = 254; // matches DB definition
const body = Buffer.from(s, 'ascii');
const head = Buffer.from([maxLen, body.length]);
return Buffer.concat([head, body, Buffer.alloc(maxLen - body.length)]);
}
msg.payload = packS7String("HELLO");
return msg;
On the PLC side, declare a DB field rxString : STRING[254]; directly inside the receive buffer. The AT-overlay trick is not necessary for strings because the S7 compiler will already lay out the two header bytes correctly.
9.2 Reading a STRING from the PLC
When the PLC sends a STRING, the first two bytes are the same header. In Node-RED:
const b = Buffer.isBuffer(msg.payload) ? msg.payload : Buffer.from(msg.payload);
const curLen = b.readUInt8(1);
msg.payload = b.slice(2, 2 + curLen).toString('ascii');
return msg;
10. Diagnosing Error 7006 (STATUS_TCP_NO_DATA / CONNECTION_ESTABLISHING)
16#7006 is the most common status value reported by users investigating OUC for the first time. It appears in two distinct contexts and means different things:
| Context | Meaning | Action |
|---|---|---|
| STATUS of TRCV_C | No new data received — this is not an error, the receive job is simply idle. | None. Wait for a frame or trigger a test send from the peer. |
| STATUS of TSEND_C / TCON | Connection establishment in progress. | Verify partner reachable, port open, firewall rule, ActiveEstablished flag. |
On S7-1500 the full set of OUC status codes is:
| Hex | Meaning |
|---|---|
| 16#0000 | Job completed without error |
| 16#7000 | No job active |
| 16#7001 | Connection establishing |
| 16#7002 | Job running / data being transferred |
| 16#7003 | Connection terminating |
| 16#7004 | Connection terminated |
| 16#7006 | No new data (TRCV) / connection establishing (TCON) |
| 16#80C4 | Temporary communications error — partner not reachable |
| 16#8181 | Same as 0x80C4 in some FW versions |
| 16#80A7 | TCP connection refused by partner |
10.1 What "I always receive code 7006, but data never arrives" really means
This symptom is reported when TRCV_C is polled and the user is interpreting the status word directly. Because 7006 is the idle status, the absence of data is the expected behavior, not a failure. Two checks confirm a healthy socket:
- Open a Wireshark capture on the Node-RED host. Filter
tcp.port == 2000. You should see the SYN/SYN-ACK/ACK handshake followed by heartbeat traffic whenCONT=TRUE. - From Node-RED, send a known payload (e.g.
Buffer.from([0x01, 0x02])) overtcp-outand watchRCVD_LENandSTATUSin the PLC's watch table.
If the handshake completes and the PLC still shows 16#7006 with RCVD_LEN = 0, the receive job is wired but the data is being dropped on the Node-RED side. The most frequent cause is mismatched port — the tcp-out node is sending to a different port than the one TCON_Param listens on.
11. Commissioning and Verification Procedure
- Compile and download the TIA project. After download, perform a STOP → RUN transition so the connection resource table is rebuilt.
- Open the Watch table and force
REQ = TRUEon theTSEND_Cinstance. ConfirmSTATUS = 16#0000andDONE = TRUEafter one cycle. - In Node-RED, wire a
debugnode to thetcp-inoutput. The bytes sent in step 2 must appear as aBuffer. - Use a
functionnode to decode the buffer per section 9 and wire a seconddebugto confirm the parsed value. - Send a value back from Node-RED with the
tcp-outnode. In the watch table,RCVD_LENshould equal the payload length and the destination buffer should be populated. - Disable and re-enable the connection on the Node-RED side to verify auto-reconnect behavior. The PLC will report
16#7003→16#7001→16#7002as the new connection is established.
12. Troubleshooting Matrix
| Symptom | Probable Cause | Resolution |
|---|---|---|
| PLC reports 16#80C4 on TSEND_C | Partner unreachable (firewall, wrong IP, wrong port) | Check route, ping from PLC, verify port on Node-RED host |
| PLC reports 16#80A7 | TCP connection refused — nothing is listening on the partner port | Start the Node-RED listener before commissioning; check tcp-in port |
| No error, no data on TRCV_C | Node-RED sending to a different port than TCON_Param | Reconcile RemotePort/LocalPort on both sides |
| INT values appear swapped or negative | Little-endian vs. big-endian mismatch | Use readInt16BE / writeInt16BE in Node-RED |
| STRING looks corrupted in the PLC | Header bytes missing or wrong length encoding | Pack with the 2-byte S7 STRING header (max + cur length) |
| TRCV_C 16#7006 persists after deploying Node-RED | PLC still holds the old socket | STOP/RUN the PLC, or cycle power, after every config change |
| Connection drops every few minutes | Node-RED's tcp-in is set to "close on idle" | Uncheck the close-on-idle option on tcp-in and tcp-out |
| Truncated frames on Node-RED tcp-in | tcp-in configured for string mode but binary data contains NUL | Switch output to "stream of Buffer" and decode manually |
| DONE never sets on TSEND_C | LEN is larger than the source data size | Match LEN to the actual byte length of the source VARIANT |
13. Performance and Resource Notes
- OUC blocks are processed in the cyclic OB; on S7-1500 they run in priority class 1 by default. Latency on a 1 ms cycle is typically 1..3 ms per send/receive job.
- Maximum frame size is 8192 bytes per
TSEND/TRCVcall on S7-1500. Larger payloads must be segmented manually in SCL. - Each OUC instance consumes one connection resource. The S7-1511 supports 64 total, shared with HMI, S7 routing, OPC UA, and WebAPI. Plan accordingly.
- For high-rate telemetry, prefer ISO-on-TCP (ConnectionType = 0x0C) — the RFC1006 header makes frame boundaries explicit and prevents accidental concatenation in the receive buffer.
- On the Node-RED side,
tcp-increates one Node.js socket per peer. Deploys do not close existing sockets unless the node is removed, which is why the connection persists across flow changes.
14. Suggested Reference Path
For deeper protocol details, the primary reference is the SIMATIC S7-1500 S7-1500/S7-1500T Motion Control - Function Manual on the Siemens Industry Online Support portal. The OUC blocks are documented in the SIMATIC S7-1500 / ET 200MP - Communication function manual, which lists every status code and the data block structure. The Node-RED side is covered in the Node-RED user guide - network nodes page, with the TCP node reference in the node-red-node-tcp flow library entry. The standard TCP/IP behavior is specified in IETF RFC 793 - Transmission Control Protocol and the Siemens-specific framing in RFC 1006 - ISO Transport on top of TCP.
FAQ
What does error 7006 mean on TRCV_C or TSEND_C?
On TRCV_C, status 16#7006 means "no new data received" — it is an idle state, not a fault. On TSEND_C or TCON, the same value means the TCP connection is still being established. Treat 7006 as informational and check RCVD_LEN or the connection state separately.
Why are my INT values wrong but BOOLs are correct in the PLC?
Byte order. S7 INTs are 16-bit big-endian, while Node.js's default is little-endian. Use readInt16BE and writeInt16BE in your function nodes so the high byte is sent first.
How do I send a STRING from Node-RED to the PLC?
Pack the string with the 2-byte S7 STRING header — a max-length byte (e.g. 254) and a current-length byte, followed by the ASCII payload padded out to the max length. On the PLC side, declare a STRING[254] field inside the receive buffer DB; the header is handled automatically by the compiler.
Do I need to restart the PLC after every TCON change?
Yes. Re-loading the project does not tear down an existing TCP connection — the old socket stays in CLOSE_WAIT and the new parameters never take effect. Either perform a STOP/RUN transition or cycle power after modifying TCON_Param.
What port should I use for OUC from Node-RED?
Any unprivileged port above 1024 that is not used by another PLC service. Common choices are 2000, 2001, 5000, and 1880. The port chosen in TCON_Param must match the port on the Node-RED tcp-in or tcp-out node exactly, including which side is the active opener.