The SIMATIC IOT2040 is an industrial IoT gateway designed by Siemens for data acquisition, edge buffering, and simple SCADA-style telemetry at the machine level. With its Intel Quark x86 CPU, Yocto-based Linux, Node-RED pre-installed, and dual Ethernet ports, it is well suited to act as a local historian for shop-floor signals that cannot be pushed continuously to a higher-level MES or cloud platform. This reference walks through the practical design, build, and commissioning of a local SQLite database administration stack on the IOT2040, exposing Node-RED, Node.js, and Express as the application and presentation layers.
1. System Overview and Use Case
The IOT2040 is positioned between field devices (PLCs, sensors, energy meters) and higher-level systems. SQLite adds a self-contained, zero-config SQL store at the edge so a single node can:
- Buffer process values when uplink connectivity drops.
- Log alarm and event transitions with millisecond timestamps.
- Serve dashboards directly from the gateway without requiring a Windows host.
- Export CSV/JSON to engineering tools such as Eclipse for offline analysis.
2. Hardware and Firmware Prerequisites
| Item | Specification |
|---|---|
| CPU | Intel Quark x1026D, 400 MHz, x86 |
| Memory | 1 GB DDR3 |
| Internal flash | 8 GB eMMC |
| External storage | microSD or USB 2.0 flash (recommended for SQLite data files) |
| Ethernet | 2 x RJ45 10/100 Mbps (eth0 service, eth1 field) |
| OS | Siemens SIMATIC IOT2040 Yocto image (Poky-based Linux) |
| Default services | Node-RED, SSH, webserver on TCP/80, Eclipse IoT oray optional |
3. Architecture and Component Topology
The application stack is a logical four-layer pipeline. Each layer communicates over loopback unless explicitly bound to an external NIC.
4. Installing SQLite on the IOT2040
- Establish SSH access as the industrial user:
ssh industrial@<iot2040-ip> - Verify the Yocto userland and architecture:
uname -a; cat /etc/os-release - Install the SQLite command-line shell and the
node-sqlite3bindings (use the Siemens-supplied opkg feed when present, otherwise deploy the pre-built binary bundle via USB):opkg update && opkg install sqlite3 node-sqlite3 - Confirm versions and capture for the asset register:
sqlite3 --versionnode -e "console.log(require('better-sqlite3/package.json').version)"(orsqlite3package version) - Place the data file on the external media to avoid wearing the internal eMMC. Mount the USB or SD card, then create the database directory:
mkdir -p /media/usb/db && cd /media/usb/db - Initialise a writable database:
sqlite3 historian.db "PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL;" - Set file permissions so the Node-RED and Express daemons can both read and write:
chown -R industrial:industrial /media/usb/db && chmod 750 /media/usb/db
sqlite3 --version audit per firmware release closes that gap. Reference the SQLite chronology page for release notes and security fixes.5. Database Schema for Process Data
Design the historian schema for write-once-tag-many, not for OLTP. Three tables cover the common case and map cleanly to Node-RED msg.payload structures.
-- tags: static description of each datapoint
CREATE TABLE tags (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
unit TEXT,
min_value REAL,
max_value REAL,
description TEXT
);
-- samples: time-series of measurements
CREATE TABLE samples (
ts INTEGER NOT NULL, -- epoch ms (UTC)
tag_id INTEGER NOT NULL,
value REAL NOT NULL,
q INTEGER NOT NULL, -- quality code 0=Good, 1=Bad, 2=Uncertain
PRIMARY KEY (ts, tag_id),
FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE
);
CREATE INDEX idx_samples_tag_ts ON samples(tag_id, ts);
-- events: alarm and operator actions
CREATE TABLE events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts INTEGER NOT NULL,
severity TEXT CHECK(severity IN ('INFO','WARN','ALARM','CRIT')),
source TEXT,
message TEXT
);
PRAGMA wal_checkpoint(TRUNCATE) in a daily systemd timer, or schedule a controlled shutdown via the IOT2040 dry-contact input.6. Node-RED Acquisition Flow
Node-RED is the IOT2040's primary edge integration tool. Use the sqlite node (pre-installed in recent Siemens images) or call the node-sqlite3 binding from a function node.
- Open Node-RED at
http://<iot2040-ip>:1880. - Drag in a Modbus/OPC UA input node, a function node for shaping, and a sqlite node bound to the
/media/usb/db/historian.dbfile. - Insert the function node payload normaliser:
const ts = Date.now(); const row = { ts, tag_id: msg.topic, value: Number(msg.payload), q: msg.q || 0 }; msg.payload = row; return msg; - Configure the sqlite node to perform
INSERT OR REPLACE INTO samples(ts, tag_id, value, q) VALUES(?,?,?,?)with parameter binding. - Add an error catch node wired to a debug pane so connection drops land in the events table.
delay node or batch insert (10 rows per transaction) — typical pattern is 50 ms tick, batch=10 → ~200 rows/s sustained.7. Node.js + Express Admin API
The Express layer exposes JSON endpoints for dashboards and converts SQL rows to CSV for engineering consumption. A minimal stack:
// server.js
const express = require('express');
const sqlite3 = require('sqlite3').verbose();
const path = require('path');
const app = express();
const db = new sqlite3.Database('/media/usb/db/historian.db');
app.use(express.static(path.join(__dirname, 'public')));
app.get('/api/tags', (req, res) => {
db.all('SELECT id, name, unit FROM tags ORDER BY name', (err, rows) => {
if (err) return res.status(500).json({ error: err.message });
res.json(rows);
});
});
app.get('/api/samples', (req, res) => {
const { tag, from, to, limit = 1000 } = req.query;
db.all(
'SELECT ts, value, q FROM samples WHERE tag_id=? AND ts BETWEEN ? AND ? ORDER BY ts DESC LIMIT ?',
[tag, +from, +to, +limit],
(err, rows) => err ? res.status(500).json({ error: err.message }) : res.json(rows)
);
});
app.get('/api/export.csv', (req, res) => {
res.setHeader('Content-Type', 'text/csv');
res.setHeader('Content-Disposition', 'attachment; filename="export.csv"');
db.each('SELECT ts, tag_id, value, q FROM samples ORDER BY ts DESC LIMIT 50000', (err, row) => {
if (err) return res.status(500).send(err.message);
res.write(`${row.ts},${row.tag_id},${row.value},${row.q}\n`);
});
res.end();
});
app.listen(3000, '0.0.0.0', () => console.log('Admin API up on :3000'));
Run with a process manager so it survives crashes:
npm install -g pm2
pm2 start server.js --name iot-admin
pm2 save && pm2 startup
8. Eclipse-Based Development and Validation
Developers can author, lint, and unit-test the Node.js layer in Eclipse with the Node.js + npm extensions. Eclipse is also the tool of choice for offline trend analysis: open the exported CSV in the Eclipse Data Tools Platform or simply parse it through an Eclipse Java/Python JUnit suite that consumes the REST API. Because the IOT2040 file system is exposed to an engineering workstation via SFTP, source files in /home/industrial/iotsrv/ map directly to an Eclipse project — round-trip edits go through SSH.
better-sqlite3 on the Quark CPU. Lock the major version in package.json.9. Browser Dashboard (Node-RED UI)
Use the node-red-dashboard palette for a zero-build visualisation layer. Common widgets:
-
ui_chart → polls
/api/samplesevery 5 s. - ui_table → renders the most recent alarm rows from events.
-
ui_dropdown → bound to
/api/tagsfor tag selector.
Authenticate the dashboard with node-red-contrib-auth and a 12-character minimum password — the IOT2040 sits inside the OT network but is reachable from the service network, so do not expose TCP/1880 without it.
10. Security Hardening and Patch Management
| Layer | Control | Detail |
|---|---|---|
| OS | Siemens firmware updates | Subscribe to the Siemens Security Advisories RSS; roll forward at the next maintenance window. |
| SQLite engine | Version audit | Run sqlite3 --version quarterly; cross-check against the SQLite chronology and current CVE advisories. |
| Data at rest | Encrypted external media | Use a hardware-encrypted USB stick or LUKS partition to keep process data off unauthenticated hosts. |
| Network | Firewall | Restrict TCP/1880, TCP/3000, TCP/80 to the service VLAN via iptables. |
| Application | RBAC | Separate Node-RED read (dashboard) from Node-RED write (admin) credentials; revoke on personnel change. |
11. Verification and Commissioning Checklist
- Insert a known tag with a step change and confirm a row exists:
sqlite3 /media/usb/db/historian.db "SELECT * FROM samples ORDER BY ts DESC LIMIT 5;" - Hit the REST API and verify the JSON response from a PC:
curl http://<iot2040-ip>:3000/api/tags - Export a CSV with sample data:
curl -OJ http://<iot2040-ip>:3000/api/export.csv - Confirm WAL mode persists across reboots:
sqlite3 /media/usb/db/historian.db "PRAGMA journal_mode;"→ expectwal. - Apply a forced failover by unplugging the uplink NIC for 60 s; data must still be queriable from the gateway and queued for backfill.
- Review
pm2 logsand Node-RED debug pane for unhandled errors.
12. Troubleshooting Matrix
| Symptom | Likely cause | Fix |
|---|---|---|
SQLITE_BUSY in Node-RED logs |
Express holding reader lock while writer commits | Set PRAGMA busy_timeout=5000 on the DB handle |
| Dashboard chart empty | CORS or wrong tag_id string |
Verify tag exists with SELECT * FROM tags WHERE name=?
|
| Database file growth uncontrolled | PRAGMA auto_vacuum not set | Run PRAGMA auto_vacuum=INCREMENTAL and a periodic VACUUM
|
| Wal file too large after power loss | Card too slow to flush | Use industrial-grade SLC media; add a daily checkpoint cron |
| Express API returns 500 on every call | DB file missing or path mismatch | Check journalctl -u pm2; confirm absolute path |
| Node-RED sqlite node red status | node-sqlite3 not built for x86 | Rebuild with npm rebuild sqlite3 against the gateway's glibc |
13. Operational Notes and Limits
- Single-process, single-writer is mandatory. SQLite is not safe for multiple processes writing concurrently with shared cache; use a tiny in-process queue and one writer.
- Keep the data file under ~50 GB. Beyond that the Quark's read amplification drops real-world query responsiveness.
- For audit trails consider the Oracle-style handbook orientation: Getting Started with Database Administration documents general DBA discipline that applies even to SQLite — backup schedule, retention policy, role separation.
- Plan capacity at 1 byte/row × samples/s × history window. Example: 50 tags × 1 Hz × 30 days ≈ 130 MB before indexes.
What SQLite version should I run on the SIMATIC IOT2040?
Use the SQLite 3.x line shipped by the current Siemens firmware image; verify with sqlite3 --version after each firmware update and cross-check against the official SQLite chronology page for security fixes before deployment.
Can SQLite be the only historian on the IOT2040?
Yes, for single-cell or machine-level buffering. For a multi-gateway plant historian use a higher-tier historian (e.g. WinCC, InfluxDB on a server) and treat the IOT2040 database as a store-and-forward buffer only.
How do I expose the database to Eclipse for analysis?
Mount the IOT2040 file system with SFTP in Eclipse Remote Systems, or call the Express JSON endpoint from a Java/Python client; CSV export via /api/export.csv is the simplest path for offline import.
Why does my better-sqlite3 install fail on the IOT2040?
Most failures stem from a Node.js version mismatch with the Quark's glibc. Pin Node.js to 10.x or 12.x, rebuild from source (npm rebuild), and verify with ldd node_modules/better-sqlite3/build/Release/better_sqlite3.node that all symbols resolve.
How do I protect the SQLite file from unauthorised access?
Restrict the IOT2040 to a service VLAN via iptables, store the data file on encrypted external media, and enforce Node-RED authentication on TCP/1880 and basic-auth on the Express API on TCP/3000.