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:
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) |
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
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
- Open Node-RED in a browser:
http://192.168.0.10:1880 - Menu → "Manage palette" → Install → search
node-red-contrib-s7 - Click Install and wait for the confirmation toast
- 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
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 |
8.1 Enable PUT/GET
Without this checkbox, every read returns W#16#0000 or a security error:
- TIA Portal → CPU properties → "Protection & Security"
- Tick "Permit access with PUT/GET communication from remote partner (PLC, HMI, OPC, ...)"
- 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,REAL0returns a value, not an exception code0x05(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:
- From the IOT2040, write
DB1,REAL12 = 27.5(the Setpoint slot) - Read
DB1,REAL12back via the S7 endpoint - Confirm the round-trip value equals
27.5 ± 1e-6 - 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
iot2000setupto reduce the attack surface. - Use a dedicated iot_s7 Linux user; the IOT2040 example image ships with root enabled by default.
passwd -l rootafter 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).jsonand 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).