Industrial mixing (touch HMI)TouchBerry PiFicherosDatalogging
Append-only CSV logging with a self-refreshing history table
Sometimes the right database is a text file. This example logs every machine activation — date, user, reason, comment — to an append-only CSV file on a TouchBerry Pi, and a watch node refreshes a dashboard table the instant the file changes. The same file later goes to USB for quality records. It mirrors a real deployment on an industrial mixing line, where this audit trail answers "who started the machine, when, and why".
One line per activation
A function node formats the timestamp, takes the user from msg.topic and the reason and comment from the activation form, then emits a single CSV line ending in \n. The file node runs in append mode with its own newline disabled, so the function fully controls the format. Free-text fields are sanitised first — commas become semicolons and line breaks become spaces — keeping the file parseable forever.
The watch node closes the loop
Instead of pushing rows to the table directly, the flow watches the CSV file itself. Any change — an activation from the dashboard, even a manual edit over SSH — triggers a re-read, and a parser function converts the full file into the array of objects ui-table expects, newest first, capped at 200 rows. The file is the single source of truth; the table is just a view of it.
Why a CSV and not a database
For a few dozen events per day, a plain CSV beats a database on every axis that matters here: zero infrastructure to install or maintain, human-readable over SSH, trivially exported to a USB drive, and openable in Excel by the quality department. When the project later needed central storage as well, the same events were simply forwarded over MQTT to SQL Server — without touching this local log.
A snippet from the implementation
Straight from the example as deployed on the TouchBerry Pi — copy it freely:
const d = new Date();
const pad = n => String(n).padStart(2, "0");
const formattedDate =
`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ` +
`${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
// Sanitize: the separator is the comma, so remove it from free-text fields
const sanitize = t => String(t || "").replace(/,/g, ";").replace(/\r?\n/g, " ");
const reason = sanitize(msg.payload.reason);
const comment = sanitize(msg.payload.comment);
msg.payload = formattedDate + "," + msg.topic + "," + reason + "," + comment + "\n";
msg.filename = "/home/pi/logs/log_activaciones.csv"; // file node in append mode
return msg;
// ============================================================
// function 2: csvToTable
// ------------------------------------------------------------
// Receives the full CSV (string) read by the read-file node and
// converts it into the array of objects expected by ui-table.
// The most recent activations are shown first.
// ============================================================
const MAX_ROWS = 200; // do not overload the HMI table
const lines = (msg.payload || "")
.split("\n")
.map(l => l.trim())
.filter(l => l.length > 0);The full example is a complete program — wiring header, setup and main loop — ready to adapt to your application.
Frequently asked questions
How do I show a CSV file as a table in Node-RED dashboard?
Read the file with a read-file node, split lines and map them to objects whose keys are the column names, then feed that array to a ui-table node.
Will the table update if something else modifies the file?
Yes. The watch node fires on any modification of the file, regardless of who wrote it, so the table always reflects the file on disk.
Does the CSV file grow forever?
In this deployment the volume is small, so yes by design. Add a monthly file rotation in the filename function if you expect high event rates.