Overview
The Siemens IOT2000 series (IOT2020 and IOT2040) is an industrial gateway platform based on the Intel Quark / ARM architecture that runs a Yocto Linux image. Because it is essentially an open Linux box with industrial certifications, the IOT2000 can host a MariaDB (MySQL-compatible) server locally and act as a data historian for an S7-1200 PLC. Node-RED provides the flow-based integration layer that reads the S7-1200 tags over the S7 communication protocol and writes them into the relational database. The pattern below is the same one shipped in the Siemens application example for the IOT2000 and is the de-facto reference architecture for small to mid-size S7-1200 data acquisition projects.
This article covers the full path: installing MariaDB on the IOT2000, configuring the S7-1200 side (PUT/GET, DB access, firewall), wiring up the Node-RED S7 node, designing the database schema, and inserting tag values on a cyclic time base. The end result is a local, time-stamped, queryable log of PLC process data.
System Architecture
Data flow for a typical deployment:
Prerequisites
- Siemens IOT2020 or IOT2040 with the Siemens IoT2000 SD card image (Examples V2.x) installed.
- S7-1200 CPU with firmware V4.0 or higher (S7-1500 is also supported by the same Node-RED S7 node).
- PUT/GET communication enabled on the S7-1200 (Project → Properties → Protection → Permit access with PUT/GET communication from remote partner).
- Node-RED preinstalled on the IOT2000 (included in the standard image at
http://<iot-ip>:1880). - Node-RED S7 node:
node-red-contrib-s7(compatible with both S7-300/400 and S7-1200/1500). - Root or sudo shell access to the IOT2000 via SSH or serial console.
- Ethernet connectivity between the IOT2000 and the S7-1200 on the same subnet.
Hardware and Software Versions
| Component | Model / Version | Notes |
|---|---|---|
| IoT Gateway | 6ES7647-0AA00-0YA2 (IOT2020) / 6ES7647-0BA00-0YA2 (IOT2040) | Intel Quark x86 (IOT2020) / ARM SoC (IOT2040) |
| Image | Siemens IoT2000 Example Image V2.6.0 or later | Includes Node-RED, mraa, opc-ua, mariaDB |
| Node-RED | v0.20.x (in image) or v3.x (upgraded) | Default port 1880 |
| S7 Node | node-red-contrib-s7 v4.x | Wrapper around node-snap7 |
| MariaDB | 10.1.x (in image) / 10.5+ available via apt | MySQL-compatible wire protocol |
| MySQL Node | node-red-node-mysql v1.x | node-red-contrib-stackhero-mysql-ts is a modern alternative |
| PLC | S7-1200 CPU 1214C / 1215C / 1217C, FW 4.2+ | Also S7-1500 supported |
| Network | TCP/IP, ISO-on-TCP port 102 (S7) | Same subnet or routed path |
Step 1 - Configure the S7-1200 Side
The S7-1200 must explicitly allow PUT/GET access because it is a passive server in this architecture. Without this permission, the S7 node will fail to read with error 0x8104 (no license / access denied).
- Open the TIA Portal project, right-click the CPU and open Properties.
- Navigate to Protection & Security.
- Check Permit access with PUT/GET communication from remote partner (PLC, HMI, OPC, ...).
- Define a global DB (e.g.
DB100 "HistData") that contains the values you want to log. Each value must have a unique name because the S7 node references tags by symbolic name. - Compile and download the hardware configuration.
- Verify the PLC is online and that its IP address is reachable from the IOT2000:
ping 192.168.0.10.
Step 2 - Install MariaDB on the IOT2000
The example image already bundles MariaDB. If the package is missing, install it from the Siemens package feed.
- SSH into the IOT2000:
ssh root@<iot-ip>. - Update the package list:
opkg update(legacy image) orapt-get update(newer image). - Install MariaDB:
opkg install mariadb-server mariadb-client. - Initialize the data directory:
mysql_install_db --user=mysql. - Start the server:
/etc/init.d/mariadb startorsystemctl start mariadb. - Secure the installation:
mysql_secure_installationand set a root password.
Verify the server is listening on TCP/3306:
netstat -tlnp | grep 3306
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 1329/mysqld
Step 3 - Create the Database and User
Connect to the MariaDB instance and create a dedicated database and a restricted user. Do not let Node-RED use the root account.
mysql -u root -p
CREATE DATABASE iot_data CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'iot_writer'@'localhost' IDENTIFIED BY 'ChangeMe!2024';
GRANT SELECT, INSERT, UPDATE ON iot_data.* TO 'iot_writer'@'localhost';
FLUSH PRIVILEGES;
EXIT;
Step 4 - Design the SQL Schema
Two tables cover the typical historian workload: one for the static tag description, one for the high-frequency values.
USE iot_data;
CREATE TABLE tags (
id INT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
description VARCHAR(255) DEFAULT NULL,
unit VARCHAR(16) DEFAULT NULL,
plc_datatype VARCHAR(16) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY uniq_name (name)
) ENGINE=InnoDB;
CREATE TABLE tag_history (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
tag_id INT UNSIGNED NOT NULL,
ts DATETIME(3) NOT NULL,
value_double DOUBLE DEFAULT NULL,
value_int BIGINT DEFAULT NULL,
value_text VARCHAR(255) DEFAULT NULL,
quality TINYINT NOT NULL DEFAULT 192,
PRIMARY KEY (id),
KEY idx_tag_ts (tag_id, ts),
CONSTRAINT fk_th_tag FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
) ENGINE=InnoDB;
INSERT INTO tags (name, description, unit, plc_datatype) VALUES
('Temperature_01', 'Tank 1 temperature', 'C', 'REAL'),
('Pressure_01', 'Line 1 pressure', 'bar','REAL'),
('Motor_Current', 'Pump 1 current', 'A', 'REAL'),
('Conveyor_Run', 'Conveyor run flag', '', 'BOOL');
The composite index on (tag_id, ts) keeps time-window queries fast (typical query: last N minutes of a single tag). DATETIME(3) stores millisecond resolution, which is sufficient for a 1 Hz poll cycle.
Step 5 - Install the Node-RED S7 and MySQL Nodes
Open the Node-RED UI at http://<iot-ip>:1880 and install the nodes from the palette manager (top-right menu → Manage palette → Install):
-
node-red-contrib-s7- S7 communication (S7-300/400/1200/1500) -
node-red-node-mysql- MySQL/MariaDB client -
node-red-dashboard- Optional: live gauges alongside the database write
Or install from the command line via the IOT2000 shell:
cd ~/.node-red
npm install node-red-contrib-s7
npm install node-red-node-mysql
node-red-restart
Step 6 - Configure the S7 Endpoint
Drop an s7 endpoint node into a new flow and double-click it. Use the following values for an S7-1200 with firmware 4.x:
| Field | Value |
|---|---|
| Address | 192.168.0.10 (your PLC IP) |
| Port | 102 (default S7) |
| Rack | 0 |
| Slot | 1 (S7-1200 CPU 1) |
| Connect type | All to single |
| Mode | Client-Server, the S7 node acts as a client |
| Name | PLC-1 |
For S7-1500 CPUs the slot is typically 1 as well. For S7-300 / S7-400 the slot depends on the CPU model (S7-300 CPU 315-2 PN/DP = slot 2).
Step 7 - Map the Variables
Drop an s7 in node (input direction, read) and add one variable per tag:
| Name | Address (DB number + offset) | Data type |
|---|---|---|
| Temperature_01 | DB100,REAL0 | REAL |
| Pressure_01 | DB100,REAL4 | REAL |
| Motor_Current | DB100,REAL8 | REAL |
| Conveyor_Run | DB100,X12.0 | BOOL |
If the S7-1200 DB is non-optimized, the offsets above are byte offsets. If the DB is optimized, the node resolves symbols by name, so use the tag symbolic name and skip the offset.
Step 8 - Build the Write Flow
Wire a function node after the s7 in node to format the payload, then a mysql node to insert one row per cycle. A simple cyclic trigger is provided by an inject node with a 1 s interval.
Function node code that builds a single multi-row INSERT (one row per tag, executed once per second):
const now = new Date().toISOString().slice(0, 23).replace('T', ' ');
const rows = [];
for (const t of ['Temperature_01', 'Pressure_01', 'Motor_Current', 'Conveyor_Run']) {
const v = msg.payload[t];
if (v === undefined) continue;
rows.push({
tag_id: 0, // will be replaced by sub-select
ts: now,
value_double: (typeof v === 'number') ? v : null,
value_int: (typeof v === 'number') ? null : null,
value_text: null
});
}
msg.payload = rows;
msg.topic = `
INSERT INTO tag_history (tag_id, ts, value_double)
SELECT id, ?, ? FROM tags WHERE name = ?`;
return msg;
The matching MySQL node should be configured in multiple statements / array input mode, with the database iot_data, user iot_writer, and password as set in step 3. The node loops over the array and executes the parameterized INSERT once per element.
Step 9 - Verify the Round Trip
- Deploy the flow in Node-RED.
- Watch the debug sidebar - the s7 in node should output a JSON object containing all four tag values once per second.
- From the IOT2000 shell, query the database:
mysql -u iot_writer -p iot_data -e "SELECT COUNT(*) FROM tag_history;" - After 60 s, the count should be approximately 240 rows (4 tags x 60 s).
- Sample a recent row:
SELECT * FROM tag_history ORDER BY id DESC LIMIT 5;
Performance and Storage Sizing
A typical historian deployment logs N tags at F Hz. Required disk space per year:
rows_per_year = N * F * 3600 * 24 * 365
bytes_per_row ~= 64 (header + DATETIME(3) + DOUBLE + INT + small overhead)
storage_GB = (rows_per_year * bytes_per_row) / 1024^3
For 50 tags at 1 Hz: 50 * 1 * 31_536_000 = 1.58 billion rows/year, which is roughly 96 GB. Add a partitioning strategy or a TTL job if you are using a single SD card:
ALTER TABLE tag_history
PARTITION BY RANGE (TO_DAYS(ts)) (
PARTITION p_old VALUES LESS THAN (TO_DAYS('2025-01-01')),
PARTITION p_2025 VALUES LESS THAN (TO_DAYS('2026-01-01')),
PARTITION p_max VALUES LESS THAN MAXVALUE
);
For the IOT2020 (Intel Quark, 1 GB RAM, 4 GB eMMC) MariaDB is fine for tens of thousands of rows. For the IOT2040 you have more headroom and can run 10 Hz sampling without issue.
Troubleshooting Matrix
| Symptom | Likely Cause | Fix |
|---|---|---|
| S7 node status: "Disconnected" / red ring | Network unreachable, wrong rack/slot, PLC firewall | Verify IP with ping, check S7-1200 protection, confirm rack 0 / slot 1 |
| Error "ISO : Invalid buffer format" | Optimized DB and absolute offset used together | Use symbolic name only on optimized blocks |
| MySQL node: "ER_ACCESS_DENIED_ERROR" | Wrong user/password, host mismatch | Re-run GRANT, ensure 'iot_writer'@'localhost' matches Node-RED host |
| MySQL node: "ECONNREFUSED 127.0.0.1:3306" | MariaDB not running or bound to socket only | Start service, confirm netstat -tlnp | grep 3306
|
| Insertions stop after some time, no error | SD card full or eMMC worn out | Add partition cleanup, check df -h
|
| All values written as NULL | Function node overwrites payload with empty object | Confirm s7 in output structure matches what the function reads |
| Node-RED unreachable on port 1880 | Service not started on boot |
systemctl enable node-red and systemctl start node-red
|
| PLC connection drops under heavy load | Too many concurrent s7 in nodes on one endpoint | Use a single s7 in with all variables, not multiple nodes |
Hardening and Production Tips
- Enable the MariaDB slow query log during commissioning to catch missing indexes:
SET GLOBAL slow_query_log = 'ON'; - Wrap the MySQL node calls in a function node that maps the boolean
Conveyor_Runto a 0/1 INTEGER if you want to chart it later. - Use
node-red-contrib-stackhero-mysql-tsif you prefer TypeScript prepared statements and a promise-based API. - Put the MariaDB data directory on a USB stick on the IOT2020 if you anticipate more than 1 GB of history (the internal eMMC is only 4 GB and has limited write endurance).
- On the S7-1200 side, use a clock byte in the DB so you can correlate PLC and IOT2000 timestamps. The IOT2000 NTP-synced time is the master clock for the historian.
- Lock down the database port:
bind-address = 127.0.0.1in/etc/mysql/my.cnfif you do not need remote access. For remote access, use SSH tunneling. - Use
innodb_flush_log_at_trx_commit = 0andsync_binlog = 0if the S7-1200 is polling at 1 Hz or faster and durability of the last second is acceptable.
Alternatives and Extensions
If the data volume is too high for a relational store, replace the MySQL INSERT with a time-series database:
-
InfluxDB via
node-red-contrib-influxdb- high write throughput, native downsampling. - TimescaleDB - PostgreSQL extension that keeps MariaDB's SQL interface but adds hypertables and compression.
- MQTT broker (Mosquitto) on the IOT2000 if you want to publish to an external broker, then let a separate consumer write to the DB.
For visualization, point Grafana directly at the MariaDB / InfluxDB instance. The S7-1200 data becomes immediately available as a dashboard.
FAQ
Can I use MySQL instead of MariaDB on the IOT2000?
Yes. The MySQL wire protocol is identical, so node-red-node-mysql works against both. MariaDB is preferred because it is the version shipped in the Siemens example image and has lower memory overhead, which matters on the 1 GB IOT2020.
Do I need a separate PC to run the database?
No. The IOT2000 runs MariaDB locally. The Siemens application example demonstrates exactly this setup: S7-1200 → IOT2040 (Node-RED + MariaDB) → optional remote MariaDB on a server. The local instance is sufficient for small projects.
Which Node-RED S7 node should I install?
Use node-red-contrib-s7 (v4.x or newer). It wraps node-snap7 and supports S7-300/400/1200/1500 over the standard ISO-on-TCP port 102. Older node-red-contrib-s7comm nodes only work with S7-300/400.
Why does the S7 node fail to connect to the S7-1200?
The most common cause is that PUT/GET access is disabled on the S7-1200. In TIA Portal open the CPU properties, go to Protection & Security, and enable "Permit access with PUT/GET communication from remote partner". Then check the rack (always 0 for S7-1200) and slot (1 for the CPU).
How fast can I poll the S7-1200 from the IOT2000?
On an IOT2040 a single s7 in node with 20-30 variables runs at 5-10 Hz without dropping frames. On the IOT2020 (Intel Quark) expect 1-2 Hz for the same load. Do not put multiple s7 in nodes on the same endpoint - use one node with all variables defined.
Can I write back to the S7-1200 from Node-RED?
Yes. Drop an s7 out node, set the same variables with a different mode, and feed it from an inject or function node. Use it for setpoint changes, mode bits, or recipe download. The S7-1200 will accept writes as long as PUT/GET is enabled.