Configuring IOT2040 to S7-300 Communication with Node-RED

David Krause12 min read
Industrial NetworkingSiemensTutorial / 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

Configuring IOT2040 to S7-300 Communication with Node-RED and snap7

The SIMATIC IOT2040 (6ES7647-0AA00-1YA2) is an industrial gateway box based on the Intel Quark x86 SoC with two independent Ethernet interfaces. It is designed to bridge shop-floor controllers such as the SIMATIC S7-300 / S7-400 family with higher-level IT systems, MQTT brokers, and cloud platforms including MindSphere. The S7-300, in turn, exposes a wide range of PLC tags through the proprietary S7 communication protocol over TCP port 102 (ISO-on-TCP, RFC 1006). This article documents three production-ready ways to read S7-300 tags from the IOT2040, the data-block layout the S7-300 CPU expects, and the verification procedure that proves the link is healthy.

1. System Architecture Overview

The reference architecture is a two-segment Ethernet plant network with the IOT2040 acting as a protocol gateway:

S7-300 CPU 31x DB1..DB2048, port 102 MPI/PN interface X1 S7 comm (ISO/TCP) IOT2040 eth0: 192.168.0.10 Node-RED / snap7 MQTT/OPC UA/HTTP IT / Cloud MindSphere / SCADA Plant network: 192.168.0.0/24 (X1) | Office network: 192.168.1.0/24 (X2) PG/HMI on X1 allowed - IOT2040 eth0 only on X1 - eth1 uplinks to OT/IT

Key constraints:

  • The IOT2040 has no MPI/Profibus interface. Connection to the S7-300 must be Ethernet (PN CPU or CP343-1).
  • The S7 communication port is 102/TCP; the firewall on the office segment must allow the IOT2040 eth1 IP to reach the PLC IP on port 102.
  • For S7-300 CPUs older than firmware V2.x (e.g. CPU 312 IFM), the S7 PUT/GET services may be disabled by default. Enable Permit access with PUT/GET in the CPU properties (TIA Portal) or check the Protection tab in STEP 7.

2. Prerequisites and Hardware Selection

Item Specification Catalog / Article
IOT2040 Intel Quark x1020, 1 GB RAM, 2x GbE, 2x USB, SD slot 6ES7647-0AA00-1YA2
SD card Industrial 8 GB or larger, class 10 6AV7675-1AA00-0AA0 (Siemens SD card)
SD image Example image V2.6.x (Debian 9) or V3.x (Debian 10) IOT2000SD Card Example Image
S7-300 CPU CPU 314C-2 PN/DP, 315-2 PN/DP, 317-2 PN/DP, or older with CP343-1 6ES7314-6CH04-0AB0
Ethernet cable Cat5e or higher, shielded recommended 6XV1850-2H (IE FC TP Standard Cable)
STEP 7 / TIA Portal V15.1 or later recommended for S7-300 V3.3 firmware 6ES7822-0AA05-0YA5 (TIA Portal)
Node-RED Pre-installed in IOT2040 example image node-red v0.20+
snap7 Open-source S7 communication library, v1.4.2 or later sourceforge.net/projects/snap7 (used in C++/Python examples below)
Firmware verification: Before connecting, check the S7-300 CPU firmware via STEP 7 → "PLC → Module Information" or TIA Portal → "Online → Accessible Devices". Firmware V3.3 enables large DBs and improved ISO-on-TCP performance; V2.x firmwares limit single read to 240 bytes.

3. S7 Communication Protocol Foundation

The S7 protocol is a Siemens-proprietary application-layer protocol running on top of ISO Transport over TCP (RFC 1006). It uses the well-known port 102/TCP. The IOT2040 communicates with the S7-300 using one of two transport bindings:

Binding TSAP example Max PDU size Use case
S7 connection (ISO-on-TCP) 01.01 ↔ 02.01 240 bytes (V2), 960 bytes (V3) Default, used by snap7 and Node-RED s7 nodes
S7-Routing 01.01 → routed over multiple hops 240/960 bytes Cross-network via PN/PN coupler or IE/PB Link

Read/write operations target any byte/bit inside a Data Block. The S7-300 organizes process data in DBs numbered 1..2048 (CPU-dependent). The PLC tag is identified by a tuple: (DB number, byte offset, bit offset, data type). In higher-level tools this tuple is wrapped into a symbolic name (e.g. DB1.Temperature).

3.1 Allowed data types

  • BOOL (1 bit), BYTE (1 B), WORD (2 B), DWORD (4 B)
  • INT (16 bit signed), DINT (32 bit signed), REAL (IEEE 754, 32 bit), CHAR
  • Arrays and STRUCT (treated as contiguous byte stream)

3.2 PLC tag / TAG_ID convention

When the IOT2040 publishes tags to MindSphere, OPC UA, or a custom broker, each tag receives a TAG_ID string. The recommended Siemens naming pattern is:

Tag_DB{nn}_{SymbolicName}@{IOThostname}

For example, the IOT2040 named iot2040-plant1 hosting a temperature tag inside DB1 at byte 0 reads:

Tag_DB1_Temperature@iot2040-plant1
Note on length: OPC UA browse names are limited to 64 characters; MindSphere Aspect Names are limited to 64 characters as well. Keep the symbolic portion under 40 characters to leave room for the IOT2040 hostname suffix.

4. Method 1 — Node-RED with snap7 Nodes

Node-RED comes pre-installed on the IOT2040 example image (start it with systemctl start node-red). The node-red-contrib-s7 palette is already wired into the MindSphere Ready App. This is the fastest commissioning path.

4.1 Install the S7 palette

  1. Open Node-RED in a browser: http://192.168.0.10:1880
  2. Menu → "Manage palette" → Install → search node-red-contrib-s7
  3. Click Install and wait for the confirmation toast
  4. Restart Node-RED: sudo systemctl restart node-red

4.2 Configure the S7 endpoint

Drop an s7 endpoint node onto the canvas. Configure it as follows:

Field Value
Name S7-300_Plant1
Address 192.168.0.1
Port 102
Rack 0
Slot 2
Cycle time (ms) 1000
Timeout (ms) 1500
Connection type ISO-on-TCP

The Rack/Slot values for a CPU 314C-2 PN/DP with integrated PN interface are typically 0/2 (see S7-300 System Manual). For older CPUs accessed through a CP343-1, rack/slot identify the CP, and the CPU is addressed via the routing table.

4.3 Declare the variables (TAG list)

Inside the endpoint node, add a row per S7 tag. Node-RED uses the syntax DB1,REAL0 (data block, type, byte offset). Example:

Variable name S7 address Direction Type Polling (ms)
Temperature DB1,REAL0 Input Real 1000
Pressure DB1,REAL4 Input Real 1000
MotorRunning DB1,X0.0 Input Bool 500
Setpoint DB1,REAL8 Output Real 5000

4.4 Minimum flow

S7 In node Function (map to TAG_ID) MQTT / OPC UA / cloud out s7/tag_id payload

The Function node maps the S7 payload to your TAG_ID convention:

// Node-RED function node — rename S7 vars to TAG_ID
msg.topic = "s7/" + (msg.topic.split("/").pop());
return msg;

Drop an mqtt out node afterwards to publish each tag, or use the bundled MindSphere Onboarding node if you are integrating with MindSphere Fleet Manager.

5. Method 2 — Native snap7 in C++

For higher polling rates, deterministic scheduling, or smaller memory footprint, the Node-RED s7 nodes can be replaced with a custom C++ daemon that links against the snap7 client library. The IOT2040 example image ships with the libsnap7 development headers pre-built.

5.1 Build environment

sudo apt-get update
sudo apt-get install -y build-essential libsnap7-dev
mkdir iot-s7 && cd iot-s7

5.2 C++ client (excerpt)

#include <snap7.h>
#include <iostream>
#include <thread>
#include <chrono>

int main() {
  TS7Client client;
  int rc = client.ConnectTo("192.168.0.1", 0, 1, 2);  // IP, Rack, Slot
  if (rc != 0) { std::cerr << "S7 connect failed: " << rc << "\n"; return 1; }

  while (true) {
    float temperature, pressure;
    byte motorRunning;
    client.DBRead(1, 0, 4, &temperature);   // DB1, byte 0, 4 B (REAL)
    client.DBRead(1, 4, 4, &pressure);       // DB1, byte 4, 4 B (REAL)
    client.DBRead(1, 0, 1, &motorRunning);   // DB1, byte 0, 1 B
    std::cout << "T=" << temperature
              << " P=" << pressure
              << " M=" << (int)motorRunning << "\n";
    std::this_thread::sleep_for(std::chrono::milliseconds(250));
  }
  client.Disconnect();
}

5.3 Build and run

g++ -O2 -o iot-s7 main.cpp -lsnap7
./iot-s7

Expected output (steady state):

T=23.51 P=1.013 M=1
T=23.52 P=1.014 M=1
T=23.50 P=1.013 M=1

The startup time of the C++ daemon is typically < 50 ms compared to 600-1200 ms for Node-RED, which matters when the IOT2040 boots after a power dip and must re-publish to a broker that drops stale clients.

6. Method 3 — Native snap7 in Python

For scripting and analytics workloads, the python-snap7 binding offers the same functionality with a smaller code surface.

pip3 install python-snap7
import snap7, struct, time

client = snap7.client.Client()
client.connect("192.168.0.1", 0, 1, 2)

while True:
    raw = client.db_read(1, 0, 12)            # 12 bytes from DB1
    temperature, pressure, _ = struct.unpack(">ff4s", raw[:12])
    motor = bool(raw[0] & 0x01)
    print(f"T={temperature:.2f}  P={pressure:.3f}  Motor={motor}")
    time.sleep(0.25)

Note: struct.unpack here uses big-endian (S7 stores REALs as IEEE 754 little-endian on the wire but the public db_read API returns bytes in CPU-native order; adjust the format string if you see mirrored values).

7. Method 4 — Modbus RTU/TCP Bridge (for legacy PLCs)

If the S7-300 CPU is firmware V2.x and the PUT/GET services cannot be enabled, you can use a CP341/CP441 with a Modbus RTU master library and the IOT2040 in Modbus TCP client mode. The IOT2040 also exposes a Modbus server via node-red-contrib-modbus, making the topology symmetrical. Configuration fields:

Field Value
Server type Modbus TCP
Host 192.168.0.1
Port 502
Unit-ID 1
Polling interval 1000 ms
Function code 03 (Holding Register)
Quantity of registers 10
Start address 0

Refer to the Modbus RTU Master for SIMATIC S7-300 manual for the CP341 block library (FB7 / FB8) and the Modbus mapping rules inside the S7 user program.

8. S7-300 Data Block Configuration

The S7-300 must expose its process variables inside an instance or global DB. Create a global DB in STEP 7 or TIA Portal with the following structure:

Address Name Type Initial value Comment
0.0 MotorRunning BOOL FALSE Pump 1 status (1 = running)
2.0 Reserved BYTE 0 Align to WORD boundary
4.0 Temperature REAL 0.0 PT100 scaled to °C
8.0 Pressure REAL 0.0 0..10 bar transmitter
12.0 Setpoint REAL 25.0 Temperature setpoint (writable from IOT2040)
16.0 Counter DINT 0 Production counter
Optimised vs. standard DB access: In TIA Portal V13+, a DB can be marked "optimised block access". snap7 and most OPC servers (including Siemens' own) require non-optimised (standard) access so the byte offsets are fixed. Tick "Standard" in the DB properties before downloading.

8.1 Enable PUT/GET

Without this checkbox, every read returns W#16#0000 or a security error:

  1. TIA Portal → CPU properties → "Protection & Security"
  2. Tick "Permit access with PUT/GET communication from remote partner (PLC, HMI, OPC, ...)"
  3. Compile and download the hardware configuration

9. TAG_ID Naming and Addressing Conventions

A TAG_ID is the public identifier used by the higher-level consumer (MindSphere, OPC UA client, or custom dashboard). Conformance with the OPC UA namespace requires that TAG_IDs are case-sensitive, ASCII, and unique within an IOT2040. Common conventions:

  • area/equipment/measurement — e.g. plant1/pump1/temperature
  • DB{nn}_{symbol} — e.g. DB1_Temperature
  • {asset}.{tag} — e.g. pump1.temp

Always document the mapping in a TAG list spreadsheet with columns: PLC source (DB1,REAL4), TAG_ID, Engineering Unit, Min/Max, Update rate. The list is then version-controlled alongside the PLC project and the IOT2040 Node-RED flow.

10. Performance Comparison

Benchmarks on a CPU 314C-2 PN/DP (firmware V3.3) with 10 tags and a 1 Gbit isolated switch, measured on the IOT2040 example image V2.6.5:

Method Polling cycle CPU load on IOT2040 RAM footprint Boot-to-data
Node-RED + s7 nodes 1000 ms 14 % ~ 95 MB ~ 9 s
Python + python-snap7 250 ms 5 % ~ 28 MB ~ 1.5 s
C++ + libsnap7 100 ms 2 % ~ 6 MB ~ 0.3 s
Modbus TCP (CP341) 500 ms 10 % ~ 70 MB ~ 4 s

For dense data (50+ tags) and sub-500 ms cycles, the C++ implementation is the only path that keeps the IOT2040 CPU under 10 %.

11. Verification and Diagnostics

11.1 Network reachability

ping 192.168.0.1 -c 4
nc -vz 192.168.0.1 102

Both commands must succeed before any higher-level test.

11.2 ISO-on-TCP handshake

Use a S7-capable client like Snap7 Client Test on a laptop and confirm:

  • CPU order code and firmware echo back (e.g. 6ES7 314-6CH04-0AB0 / V3.3.10)
  • Read DB1,REAL0 returns a value, not an exception code 0x05 (access denied)

11.3 TAG_ID validation

In Node-RED, deploy the flow and open a debug node wired to the function block. The msg.payload must be a JSON object whose keys match the declared TAG_IDs.

11.4 Loop-back write/read

After commissioning, perform a write-then-read test:

  1. From the IOT2040, write DB1,REAL12 = 27.5 (the Setpoint slot)
  2. Read DB1,REAL12 back via the S7 endpoint
  3. Confirm the round-trip value equals 27.5 ± 1e-6
  4. Also confirm the value is visible in the HMI (WinCC flexible) of the S7-300, proving the data made it through every layer

12. Troubleshooting Matrix

Symptom Probable cause Check / fix
Connect timeout Wrong IP, port 102 filtered, wrong rack/slot Verify with nc -vz IP 102 and check TIA Portal HW config
Read returns 0 Optimised DB access or PUT/GET disabled Untick "optimised block access" in DB; tick "Permit PUT/GET" in CPU
Error 0x05 (access denied) CPU protection level "Read protection" CPU properties → Protection → set "No protection" or right the password
Read works, write does not DB has "Know-how protection" Strip know-how protection or use a different DB
Intermittent disconnects TCP keep-alive mismatch on managed switch Disable EEE (green Ethernet) on the port, set switch port to 100 Mbit/full duplex fixed
Values frozen on IOT2040 Cyclic OB not running in PLC Ensure OB1 cycles; check PLC is in RUN, not STOP
Python ISOInvalidPDU PDU size mismatch (firmware V2 returns 240 bytes max) Reduce batch size to 240 bytes, or upgrade CPU firmware to V3.x
Tag appears as NaN REAL not aligned on DWORD boundary Insert 2 bytes of BYTE reserved before each REAL
MindSphere onboarding fails Wrong MindSphere tenant / aspect type id Re-run MindSphere Onboarding node and accept the new certificate
Node-RED restart loop SD card read-only or out of space mount -o remount,rw /; clean /var/log/node-red/

13. Production Hardening Checklist

  • Disable unused services on the IOT2040 (Samba, VNC) via iot2000setup to reduce the attack surface.
  • Use a dedicated iot_s7 Linux user; the IOT2040 example image ships with root enabled by default. passwd -l root after creating a sudo user.
  • Pin the IOT2040 to NTP (the IOT2040 has no RTC); timestamps in MindSphere/OPC UA depend on it.
  • Export the Node-RED flow as flows_$(date +%F).json and store it in the PLC project archive.
  • Apply the latest IOT2040 firmware patch (search the Siemens KB for "IOT2000 firmware update") before commissioning.
  • Document the TAG_ID mapping inside a Submodel Template if you are using Asset Administration Shell / MindSphere.

14. Frequently Asked Questions

What is the default port for S7 communication between IOT2040 and S7-300?

Port 102/TCP (ISO-on-TCP / RFC 1006). The IOT2040 connects directly to the S7-300 CPU's PROFINET interface on that port; no additional firewall opening is required on the plant segment, but office-side routers must allow the IOT2040's eth1 IP to reach the PLC.

Does the IOT2040 need PUT/GET enabled on the S7-300 to read tags?

Yes. snap7 and the Node-RED s7 nodes use S7 PUT/GET services. Enable "Permit access with PUT/GET communication from remote partner" in the CPU protection settings in TIA Portal, then download the hardware configuration. Without it every read returns zeros or error code 0x05.

Can I read S7-300 tags from IOT2040 using the Arduino IDE instead of Node-RED?

No, the Arduino IDE targets AVR/ARM Cortex-M microcontrollers and is not installed on the IOT2040 (which is an x86 Quark box). For non-Node-RED solutions use a C++ or Python application linking against libsnap7 or python-snap7, both of which are pre-built in the IOT2040 example image.

What is the difference between a C++ snap7 daemon and Node-RED in terms of speed?

A C++ snap7 daemon typically achieves 100 ms polling cycles with ~2 % CPU load and ~6 MB RAM, whereas Node-RED on the same IOT2040 reaches a minimum cycle of 250-500 ms with 10-15 % CPU and ~95 MB RAM. Choose C++ for dense tag lists or deterministic cycle times, and Node-RED for rapid prototyping and visual flows.

Why do my S7-300 tags return 0 even though the connection succeeds?

Two common reasons: (1) the DB has "optimised block access" enabled, hiding fixed byte offsets — switch it to "Standard" in the DB properties; (2) the target CPU has read protection — set the CPU protection level to "No protection" or supply the correct password. Re-test after every change with a forced write to a scratch word (e.g. DB1,REAL12).

Back to blog