Siemens IOT2000 MariaDB: S7-1200 Data Logging with Node-RED

David Krause11 min read
Data AcquisitionSiemensTutorial / 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

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:

S7-1200 CPU DB / Merker / I/O PUT/GET enabled ISO-on-TCP (port 102) IOT2020 / IOT2040 Node-RED runtime node-red-contrib-s7 MariaDB server MySQL wire protocol (3306) MariaDB / MySQL Tables: tags, history SQL queries / Grafana Time-stamped insert on each poll cycle

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).

  1. Open the TIA Portal project, right-click the CPU and open Properties.
  2. Navigate to Protection & Security.
  3. Check Permit access with PUT/GET communication from remote partner (PLC, HMI, OPC, ...).
  4. 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.
  5. Compile and download the hardware configuration.
  6. Verify the PLC is online and that its IP address is reachable from the IOT2000: ping 192.168.0.10.
Optimization access: If the DB is set to "Optimized block access" (the TIA Portal default), the S7 node can still read it as long as you reference tags by name in the S7 node configuration. Standard access blocks (non-optimized) are also supported and are required if you want to read I/Q/M areas by absolute address.

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.

  1. SSH into the IOT2000: ssh root@<iot-ip>.
  2. Update the package list: opkg update (legacy image) or apt-get update (newer image).
  3. Install MariaDB: opkg install mariadb-server mariadb-client.
  4. Initialize the data directory: mysql_install_db --user=mysql.
  5. Start the server: /etc/init.d/mariadb start or systemctl start mariadb.
  6. Secure the installation: mysql_secure_installation and 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.

inject (1s) s7 in (read tags) function (format SQL) mysql INSERT debug / dashboard gauge

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

  1. Deploy the flow in Node-RED.
  2. Watch the debug sidebar - the s7 in node should output a JSON object containing all four tag values once per second.
  3. From the IOT2000 shell, query the database: mysql -u iot_writer -p iot_data -e "SELECT COUNT(*) FROM tag_history;"
  4. After 60 s, the count should be approximately 240 rows (4 tags x 60 s).
  5. 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_Run to a 0/1 INTEGER if you want to chart it later.
  • Use node-red-contrib-stackhero-mysql-ts if 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.1 in /etc/mysql/my.cnf if you do not need remote access. For remote access, use SSH tunneling.
  • Use innodb_flush_log_at_trx_commit = 0 and sync_binlog = 0 if 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.

Back to blog